您好,登錄后才能下訂單哦!
本篇內容介紹了“動態高并發時推薦ReentrantLock而不是Synchronized的原因有哪些”的有關知識,在實際案例的操作過程中,不少人都會遇到這樣的困境,接下來就讓小編帶領大家學習一下如何處理這些情況吧!希望大家仔細閱讀,能夠學有所成!
Synchronized 和 ReentrantLock 大家應該都不陌生了,作為java中最常用的本地鎖,最初版本中 ReentrantLock 的性能是遠遠強于 Synchronized 的,后續java在一次次的版本迭代中 對 Synchronized 進行了大量的優化,直到 jdk1.6 之后,兩種鎖的性能已經相差無幾,甚至 Synchronized 的自動釋放鎖會更好用。
在面試時被問到 Synchronized 和 ReentrantLock 的使用選擇時,很多朋友都脫口而出的說用 Synchronized ,甚至在我面試的時候問面試者,也很少有人能夠答出所以然來,moon 想說,這可不一定,只對標題感興趣的同學可以直接劃到最后,我可不是標題黨~
在 java 代碼中 synchronized 的使用是非常簡單的
1.直接貼在方法上
2.貼在代碼塊兒上
來看一張圖
在多線程運行過程中,線程會去先搶對象的監視器,這個監視器是對象獨有的,其實就相當于一把鑰匙,搶到了,那你就獲得了當前代碼塊兒的執行權。
其他沒有搶到的線程會進入隊列(SynchronizedQueue)當中等待,等待當前線程執行完后,釋放鎖.
最后當前線程執行完畢后通知出隊然后繼續重復當前過程.
從 jvm 的角度來看 monitorenter 和 monitorexit 指令代表著代碼的執行與結束。
SynchronizedQueue 是一個比較特殊的隊列,它沒有存儲功能,它的功能就是維護一組線程,其中每個插入操作必須等待另一個線程的移除操作,同樣任何一個移除操作都等待另一個線程的插入操作。因此此隊列內部其 實沒有任何一個元素,或者說容量是0,嚴格說并不是一種容器。由于隊列沒有容量,因此不能調用 peek 操作,因為只有移除元素時才有元素。
喝酒的時候,先把酒倒入酒盅,然后再倒入酒杯,這就是正常的隊列。
喝酒的時候,把酒直接倒入酒杯,這就是 SynchronizedQueue 。
這個例子應該很清晰易懂了,它的好處就是可以直接傳遞,省去了一個第三方傳遞的過程。
在 jdk1.6 以前,Synchronized 是一個重量級鎖,還是先貼一張圖
這就是為什么說,Synchronized 是一個重量級鎖的原因,因為每一次鎖的資源都是直接和 cpu 去申請的,而 cpu 的鎖數量是固定的,當 cpu 鎖資源使用完后還會進行鎖等待,這是一個非常耗時的操作。
但是在jdk1.6,針對代碼層面進行了大量的優化,也就是我們常說的鎖升級的過程。
這就是一個鎖升級的過程,我們簡單的說說:
無鎖:對象一開始就是無鎖狀態。
偏向鎖:相當于給對象貼了一個標簽(將自己的線程 id 存入對象頭中),下次我再進來時,發現標簽是我的,我就可以繼續使用了。
自旋鎖:想象一下有一個廁所,里面有一個人在,你很想上但是只有一個坑位,所以你只能徘徊等待,等那個人出來以后,你就可以使用了。 這個自旋是使用 cas 來保證原子性的,關于 cas 我這里就不再贅述了。
重量級鎖:直接向 cpu 去申請申請鎖,其他的線程都進入隊列中等待。
偏向鎖:一個線程獲取鎖時會由無鎖升級為偏向鎖
自旋鎖:當產生線程競爭時由偏向鎖升級為自旋鎖,想象一下 while(true) ;
重量級鎖:當線程競爭到達一定數量或超過一定時間時,晉升為重量級鎖
這張圖是對象頭中 markword 的數據結構,鎖的信息就是在這里存放的,很清楚的表明了鎖在升級的時候鎖信息的變動,其實就是通過二進制的數值,來對對象進行一個標記,每個數值代表一種狀態。
這個問題和我們的題目就有很大的關聯了。
在 HotSpot 虛擬機中是有鎖降級的,但是僅僅只發生在 STW 的時候,只有垃圾回收線程能夠觀測到它,也就是說,在我們正常使用的過程中是不會發生鎖降級的,只有在 GC 的時候才會降級。
所以題目的答案,你懂了嗎?哈哈,我們接著往下走。
ReentrantLock 的使用也是非常簡單的,與 Synchronized 的不同就是需要自己去手動釋放鎖,為了保證一定釋放,所以通常都是和 try~finally 配合使用的。
ReentrantLock 意為可重入鎖,說起 ReentrantLock 就不得不說 AQS ,因為其底層就是使用 AQS 去實現的。
ReentrantLock有兩種模式,一種是公平鎖,一種是非公平鎖。
公平模式下等待線程入隊列后會嚴格按照隊列順序去執行
非公平模式下等待線程入隊列后有可能會出現插隊情況
這就是ReentrantLock的結構圖,我們看這張圖其實是很簡單的,因為主要的實現都交給AQS去做了,我們下面著重聊一下AQS。
AQS(AbstractQueuedSynchronizer): AQS 可以理解為就是一個可以實現鎖的框架。
簡單的流程理解:
公平鎖:
第一步:獲取狀態的 state 的值。
如果 state=0 即代表鎖沒有被其它線程占用,執行第二步。
如果 state!=0 則代表鎖正在被其它線程占用,執行第三步。
第二步:判斷隊列中是否有線程在排隊等待。
如果不存在則直接將鎖的所有者設置成當前線程,且更新狀態 state 。
如果存在就入隊。
第三步:判斷鎖的所有者是不是當前線程。
如果是則更新狀態 state 的值。
如果不是,線程進入隊列排隊等待。
非公平鎖:
第一步:獲取狀態的 state 的值。
如果 state=0 即代表鎖沒有被其它線程占用,則設置當前鎖的持有者為當前線程,該操作用 CAS 完成。
如果不為0或者設置失敗,代表鎖被占用進行下一步。
此時獲取 state 的值,
如果是,則給state+1,獲取鎖
如果不是,則進入隊列等待
如果是0,代表剛好線程釋放了鎖,此時將鎖的持有者設為自己
如果不是0,則查看線程持有者是不是自己
讀完以上的部分相信你對AQS已經有了一個比較清楚的概念了,所以我們來聊聊小細節。
AQS使用state同步狀態(0代表無鎖,1代表有),并暴露出 getState 、 setState 以及 compareAndSet 操作來讀取和更新這個狀態,使得僅當同步狀態擁有一個期望值的時候,才會被原子地設置成新值。
當有線程獲取鎖失敗后,AQS是通過一個雙向的同步隊列來完成同步狀態的管理,就被添加到隊列末尾。
這是定義頭尾節點的代碼,我們可以先使用 volatile 去修飾的,就是保證讓其他線程可見,AQS 實際上就是修改頭尾兩個節點來完成入隊和出隊操作的。
AQS 在鎖的獲取時,并不一定只有一個線程才能持有這個鎖,所以此時有了獨占模式和共享模式的區別,我們本篇文章中的 ReentrantLock 使用的就是獨占模式,在多線程的情況下只會有一個線程獲取鎖。
獨占模式的流程是比較簡單的,就根據state是否為0來判斷是否有線程已經獲得了鎖,沒有就阻塞,有就繼續執行后續代碼邏輯。
共享模式的流程根據state是否大于0來判斷是否有線程已經獲得了鎖,如果不大于0,就阻塞,如果大于0,通過CAS的原子操作來自減state的值,然后繼續執行后續代碼邏輯。
其實ReentrantLock和Synchronized最核心的區別就在于Synchronized適合于并發競爭低的情況,因為Synchronized的鎖升級如果最終升級為重量級鎖在使用的過程中是沒有辦法消除的,意味著每次都要和cpu去請求鎖資源,而ReentrantLock主要是提供了阻塞的能力,通過在高并發下線程的掛起,來減少競爭,提高并發能力,所以我們文章標題的答案,也就顯而易見了。
synchronized是一個關鍵字,是由jvm層面去實現的,而ReentrantLock是由java api去實現的。
synchronized是隱式鎖,可以自動釋放鎖,ReentrantLock是顯式鎖,需要手動釋放鎖。
ReentrantLock可以讓等待鎖的線程響應中斷,而synchronized卻不行,使用synchronized時,等待的線程會一直等待下去,不能夠響應中斷。
ReentrantLock可以獲取鎖狀態,而synchronized不能。
其實題目的答案就在上一欄目的第一條,也是核心的區別,synchronized升級為重量級鎖后無法在正常情況下完成降級,而ReentrantLock是通過阻塞來提高性能的,在設計模式上就體現出了對多線程情況的支持。
“動態高并發時推薦ReentrantLock而不是Synchronized的原因有哪些”的內容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業相關的知識可以關注億速云網站,小編將為大家輸出更多高質量的實用文章!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。