java中鎖機制詳解

java主流鎖大致有以下幾種:

1. 樂觀鎖VS悲觀鎖

從概念上講

樂觀鎖:在使用數(shù)據(jù)的時候默認其他線程不會同時修改數(shù)據(jù),所以不加鎖。只有在修改數(shù)據(jù)之前判斷該數(shù)據(jù)之前有無更新,沒有就繼續(xù)修改數(shù)據(jù)。
樂觀鎖在Java中是通過使用無鎖編程來實現(xiàn),最常采用的是CAS算法,Java原子類中的遞增操作就通過CAS自旋實現(xiàn)的。

悲觀鎖:在獲取數(shù)據(jù)時加鎖,確保線程安全。
Java中,synchronized關(guān)鍵字和Lock的實現(xiàn)類都是悲觀鎖。

應(yīng)用場景

  • 悲觀鎖適合寫操作多的場景,先加鎖可以保證寫操作時數(shù)據(jù)正確。
  • 樂觀鎖適合讀操作多的場景,不加鎖的特點能夠使其讀操作的性能大幅提升。

代碼實現(xiàn)

CAS介紹
Compare And Swap(比較與交換),是一種無鎖算法。在不使用鎖(沒有線程被阻塞)的情況下實現(xiàn)多線程之間的變量同步。java.util.concurrent包中的原子類就是通過CAS來實現(xiàn)了樂觀鎖。
CAS算法涉及到三個操作數(shù):需要讀寫的內(nèi)存值 V ||進行比較的值 A ||要寫入的新值 B。
當且僅當 V 的值等于 A 時,CAS通過原子方式用新值B來更新V的值,否則不會執(zhí)行任何操作。
CAS的弊端:
(1)ABA問題:當需要進行比較的值A(chǔ),由A->B->A時,CAS算法檢查時,無法發(fā)現(xiàn)其改變過的。補救措施,跟個時間戳。
(2)循環(huán)時間長開銷大
(3)只能保證一個共享變量的原子操作

2.公平鎖VS非公平鎖

從概念上講

公平鎖:多個線程按照申請鎖的順序,排隊獲取鎖。先申請先獲取。優(yōu)點是所有線程都能得到鎖,不會餓死。缺點是相對于非公平鎖,整體吞吐效率低,CPU喚醒阻塞線程的開銷大。

非公平鎖:多個線程加鎖時,會嘗試獲取鎖。獲取不到會去排隊等待,若此時鎖正好可用,便無阻塞獲取鎖。優(yōu)點是減少了喚醒線程的開銷,整體吞吐效率變高。缺點是線程有可能會餓死或很久之后才獲得鎖。

從代碼實現(xiàn)上看

ReentrantLock的源碼為例:


內(nèi)部類Sync繼承AQS(AbstractQueuedSynchronizer),添加鎖和釋放鎖的大部分操作實際上都是在Sync中實現(xiàn)的。它有公平鎖FairSync和非公平鎖NonfairSync兩個子類。ReentrantLock默認使用非公平鎖,也可以通過構(gòu)造器來顯示的指定使用公平鎖。

接下來看一下公平鎖與非公平鎖的加鎖方法的源碼:


公平鎖與非公平鎖的lock()方法唯一的區(qū)別就在于公平鎖在獲取同步狀態(tài)時多了一個限制條件:hasQueuedPredecessors()。深入了解:

該方法主要做一件事情:主要是判斷當前線程是否位于同步隊列中的第一個。如果是則返回true,否則返回false。

綜上,公平鎖就是通過同步隊列來實現(xiàn)多個線程按照申請鎖的順序來獲取鎖,從而實現(xiàn)公平的特性。非公平鎖加鎖時不考慮排隊等待問題,直接嘗試獲取鎖,所以存在后申請卻先獲得鎖的情況。

3.可重入鎖VS非可重入鎖

可重入鎖又名遞歸鎖,是指在同一個線程在外層方法獲取鎖的時候,再進入該線程的內(nèi)層方法會自動獲取鎖(前提鎖對象得是同一個對象或者class),不會因為之前已經(jīng)獲取過還沒釋放而阻塞。優(yōu)點一定程度上避免了死鎖。舉例:


在上面的代碼中,類中的兩個方法都是被內(nèi)置鎖synchronized修飾的,doSomething()方法中調(diào)用doOthers()方法。因為內(nèi)置鎖是可重入的,所以同一個線程在調(diào)用doOthers()時可以直接獲得當前對象的鎖,進入doOthers()進行操作。

如果是一個不可重入鎖,那么當前線程在調(diào)用doOthers()之前需要將執(zhí)行doSomething()時獲取當前對象的鎖釋放掉,實際上該對象鎖已被當前線程所持有,且無法釋放。所以此時會出現(xiàn)死鎖。

可重入鎖的實現(xiàn)原理

ReentrantLock和synchronized都是重入鎖,那么我們通過重入鎖ReentrantLock以及非可重入鎖NonReentrantLock的源碼來對比分析一下為什么非可重入鎖在重復(fù)調(diào)用同步資源時會出現(xiàn)死鎖。



ReentrantLock和NonReentrantLock都繼承父類AQS,其父類AQS中維護了一個同步狀態(tài)status來計數(shù)重入次數(shù),status初始值為0。

當線程嘗試獲取鎖時,可重入鎖先嘗試獲取并更新status值,如果status == 0表示沒有其他線程在執(zhí)行同步代碼,則把status置為1,當前線程開始執(zhí)行。如果status != 0,則判斷當前線程是否是獲取到這個鎖的線程,如果是的話執(zhí)行status+1,且當前線程可以再次獲取鎖。而非可重入鎖是直接去獲取并嘗試更新當前status的值,如果status != 0的話會導(dǎo)致其獲取鎖失敗,當前線程阻塞。

釋放鎖時,可重入鎖同樣先獲取當前status的值,在當前線程是持有鎖的線程的前提下。如果status-1 == 0,則表示當前線程所有重復(fù)獲取鎖的操作都已經(jīng)執(zhí)行完畢,然后該線程才會真正釋放鎖。而非可重入鎖則是在確定當前線程是持有鎖的線程之后,直接將status置為0,將鎖釋放。

4.其他

自旋鎖 VS 適應(yīng)性自旋鎖
無鎖 VS 偏向鎖 VS 輕量級鎖 VS 重量級鎖
獨享鎖 VS 共享鎖

5.結(jié)語

限于時間精力以及個人水平,還有一些鎖不再做深入探究。java中的鎖已經(jīng)做了很好的封裝,熟悉各種鎖的實現(xiàn)原理,在不同場景下選擇不同的鎖。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容