目錄

1.synchronized同步鎖

2.ReentrantLock重入鎖

3.ReadWriteLock讀寫鎖

4.StampedLock戳鎖(目前沒找到合適的名字,先這么叫吧...)

5.總結(jié)

=======正文分割線==========

為了更好的支持并發(fā)程序,JDK內(nèi)部提供了多種鎖。本文總結(jié)4種鎖。

1.synchronized同步鎖

使用

synchronized本質(zhì)上就2種鎖:

1.鎖同步代碼塊

2.鎖方法

可用object.wait() object.notify()來操作線程等待喚醒

原理:synchronized細(xì)節(jié)的描述傳送門:jdk源碼剖析三:鎖Synchronized

性能和建議:JDK6之后,在并發(fā)量不是特別大的情況下,性能中等且穩(wěn)定。建議新手使用。

2.ReentrantLock重入鎖(Lock接口)

使用:ReentrantLock是Lock接口的實(shí)現(xiàn)類。Lock接口的核心方法是lock(),unlock(),tryLock()??捎肅ondition來操作線程:

大數(shù)據(jù)培訓(xùn),云培訓(xùn),數(shù)據(jù)挖掘培訓(xùn),云計(jì)算培訓(xùn),高端軟件開發(fā)培訓(xùn),項(xiàng)目經(jīng)理培訓(xùn)

如上圖,await()和object.wait()類似,singal()和object.notify()類似,singalAll()和object.notifyAll()類似

原理核心類AbstractQueuedSynchronizer,通過構(gòu)造一個(gè)基于阻塞的CLH隊(duì)列容納所有的阻塞線程,而對(duì)該隊(duì)列的操作均通過Lock-Free(CAS)操作,但對(duì)已經(jīng)獲得鎖的線程而言,ReentrantLock實(shí)現(xiàn)了偏向鎖的功能。

性能和建議:性能中等,建議需要手動(dòng)操作線程時(shí)使用。

 

3.ReentrantReadWriteLock可重入讀寫鎖(ReadWriteLock接口)

使用:ReentrantReadWriteLock是ReadWriteLock接口的實(shí)現(xiàn)類。ReadWriteLock接口的核心方法是readLock(),writeLock()。實(shí)現(xiàn)了并發(fā)讀、互斥寫。但讀鎖會(huì)阻塞寫鎖,是悲觀鎖的策略。

原理類圖如下:

大數(shù)據(jù)培訓(xùn),云培訓(xùn),數(shù)據(jù)挖掘培訓(xùn),云計(jì)算培訓(xùn),高端軟件開發(fā)培訓(xùn),項(xiàng)目經(jīng)理培訓(xùn)

大數(shù)據(jù)培訓(xùn),云培訓(xùn),數(shù)據(jù)挖掘培訓(xùn),云計(jì)算培訓(xùn),高端軟件開發(fā)培訓(xùn),項(xiàng)目經(jīng)理培訓(xùn)

JDK1.8下,如圖ReentrantReadWriteLock有5個(gè)靜態(tài)方法:

  • Sync:繼承于經(jīng)典的AbstractQueuedSynchronizer(傳說中的AQS),是一個(gè)抽象類,包含2個(gè)抽象方法readerShouldBlock();writerShouldBlock()

  • FairSync和NonfairSync:繼承于Sync,分別實(shí)現(xiàn)了公平/非公平鎖。

  • ReadLock和WriteLock:都是Lock實(shí)現(xiàn)類,分別實(shí)現(xiàn)了讀、寫鎖。ReadLock是共享的,而WriteLock是獨(dú)占的。于是Sync類覆蓋了AQS中獨(dú)占和共享模式的抽象方法(tryAcquire/tryAcquireShared等),用同一個(gè)等待隊(duì)列來維護(hù)讀/寫排隊(duì)線程,而用一個(gè)32位int state標(biāo)示和記錄讀/寫鎖重入次數(shù)--Doug Lea把狀態(tài)的高16位用作讀鎖,記錄所有讀鎖重入次數(shù)之和,低16位用作寫鎖,記錄寫鎖重入次數(shù)。所以無論是讀鎖還是寫鎖最多只能被持有65535次。

性能和建議:適用于讀多寫少的情況。性能較高。

  • 公平性

  1. 非公平鎖(默認(rèn)),為了防止寫線程餓死,規(guī)則是:當(dāng)?shù)却?duì)列頭部結(jié)點(diǎn)是獨(dú)占模式(即要獲取寫鎖的線程)時(shí),只有獲取獨(dú)占鎖線程可以搶占,而試圖獲取共享鎖的線程必須進(jìn)入隊(duì)列阻塞;當(dāng)隊(duì)列頭部結(jié)點(diǎn)是共享模式(即要獲取讀鎖的線程)時(shí),試圖獲取獨(dú)占和共享鎖的線程都可以搶占。

  2. 公平鎖,利用AQS的等待隊(duì)列,線程按照FIFO的順序獲取鎖,因此不存在寫線程一直等待的問題。

  • 重入性:讀寫鎖均是可重入的,讀/寫鎖重入次數(shù)保存在了32位int state的高/低16位中。而單個(gè)讀線程的重入次數(shù),則記錄在ThreadLocalHoldCounter類型的readHolds里。

  • 鎖降級(jí):寫線程獲取寫入鎖后可以獲取讀取鎖,然后釋放寫入鎖,這樣就從寫入鎖變成了讀取鎖,從而實(shí)現(xiàn)鎖降級(jí)。

  • 鎖獲取中斷:讀取鎖和寫入鎖都支持獲取鎖期間被中斷。

  • 條件變量:寫鎖提供了條件變量(Condition)的支持,這個(gè)和獨(dú)占鎖ReentrantLock一致,但是讀鎖卻不允許,調(diào)用readLock().newCondition()會(huì)拋出UnsupportedOperationException異常。

應(yīng)用:

大數(shù)據(jù)培訓(xùn),云培訓(xùn),數(shù)據(jù)挖掘培訓(xùn),云計(jì)算培訓(xùn),高端軟件開發(fā)培訓(xùn),項(xiàng)目經(jīng)理培訓(xùn)

4.StampedLock戳鎖

使用

StampedLock控制鎖有三種模式(排它寫,悲觀讀,樂觀讀),一個(gè)StampedLock狀態(tài)是由版本和模式兩個(gè)部分組成,鎖獲取方法返回一個(gè)數(shù)字作為票據(jù)stamp,它用相應(yīng)的鎖狀態(tài)表示并控制訪問。下面是JDK1.8源碼自帶的示例:

大數(shù)據(jù)培訓(xùn),云培訓(xùn),數(shù)據(jù)挖掘培訓(xùn),云計(jì)算培訓(xùn),高端軟件開發(fā)培訓(xùn),項(xiàng)目經(jīng)理培訓(xùn)

 1 public class StampedLockDemo { 2     //一個(gè)點(diǎn)的x,y坐標(biāo) 3     private double x,y; 4     private final StampedLock sl = new StampedLock(); 5  6     //【寫鎖(排它鎖)】 7     void move(double deltaX,double deltaY) {// an exclusively locked method  8         /**stampedLock調(diào)用writeLock和unlockWrite時(shí)候都會(huì)導(dǎo)致stampedLock的stamp值的變化 9          * 即每次+1,直到加到最大值,然后從0重新開始 
10          **/11         long stamp =sl.writeLock(); //寫鎖12         try {13             x +=deltaX;14             y +=deltaY;15         } finally {16             sl.unlockWrite(stamp);//釋放寫鎖17         }18     }19 20     //【樂觀讀鎖】21     double distanceFromOrigin() { // A read-only method22         /**23          * tryOptimisticRead是一個(gè)樂觀的讀,使用這種鎖的讀不阻塞寫24          * 每次讀的時(shí)候得到一個(gè)當(dāng)前的stamp值(類似時(shí)間戳的作用)25          */26         long stamp = sl.tryOptimisticRead();27         //這里就是讀操作,讀取x和y,因?yàn)樽x取x時(shí),y可能被寫了新的值,所以下面需要判斷28         double currentX = x, currentY = y;29         /**如果讀取的時(shí)候發(fā)生了寫,則stampedLock的stamp屬性值會(huì)變化,此時(shí)需要重讀,30          * 再重讀的時(shí)候需要加讀鎖(并且重讀時(shí)使用的應(yīng)當(dāng)是悲觀的讀鎖,即阻塞寫的讀鎖)31          * 當(dāng)然重讀的時(shí)候還可以使用tryOptimisticRead,此時(shí)需要結(jié)合循環(huán)了,即類似CAS方式32          * 讀鎖又重新返回一個(gè)stampe值*/33         if (!sl.validate(stamp)) {//如果驗(yàn)證失?。ㄗx之前已發(fā)生寫)34             stamp = sl.readLock(); //悲觀讀鎖35             try {36                 currentX = x;37                 currentY = y;38             }finally{39                 sl.unlockRead(stamp);//釋放讀鎖40             }41         }42         //讀鎖驗(yàn)證成功后執(zhí)行計(jì)算,即讀的時(shí)候沒有發(fā)生寫43         return Math.sqrt(currentX *currentX + currentY *currentY);44     }45 46     //【悲觀讀鎖】47     void moveIfAtOrigin(double newX, double newY) { // upgrade48         // 讀鎖(這里可用樂觀鎖替代)49         long stamp = sl.readLock();50         try {51             //循環(huán),檢查當(dāng)前狀態(tài)是否符合52             while (x == 0.0 && y == 0.0) {53             /**54              * 轉(zhuǎn)換當(dāng)前讀戳為寫戳,即上寫鎖55              * 1.寫鎖戳,直接返回寫鎖戳56              * 2.讀鎖戳且寫鎖可獲得,則釋放讀鎖,返回寫鎖戳57              * 3.樂觀讀戳,當(dāng)立即可用時(shí)返回寫鎖戳58              * 4.其他情況返回059              */60             long ws = sl.tryConvertToWriteLock(stamp);61             //如果寫鎖成功62             if (ws != 0L) {63               stamp = ws;// 替換票據(jù)為寫鎖64               x = newX;//修改數(shù)據(jù)65               y = newY;66               break;67             }68             //轉(zhuǎn)換為寫鎖失敗69             else {70                 //釋放讀鎖71                 sl.unlockRead(stamp);72                 //獲取寫鎖(必要情況下阻塞一直到獲取寫鎖成功)73                 stamp = sl.writeLock();74             }75           }76         } finally {77             //釋放鎖(可能是讀/寫鎖)78             sl.unlock(stamp);79         }80     }81 }

大數(shù)據(jù)培訓(xùn),云培訓(xùn),數(shù)據(jù)挖掘培訓(xùn),云計(jì)算培訓(xùn),高端軟件開發(fā)培訓(xùn),項(xiàng)目經(jīng)理培訓(xùn)

 

原理

StampedLockd的內(nèi)部實(shí)現(xiàn)是基于CLH鎖的,一種自旋鎖,保證沒有饑餓且FIFO。

CLH鎖原理:鎖維護(hù)著一個(gè)等待線程隊(duì)列,所有申請(qǐng)鎖且失敗的線程都記錄在隊(duì)列。一個(gè)節(jié)點(diǎn)代表一個(gè)線程,保存著一個(gè)標(biāo)記位locked,用以判斷當(dāng)前線程是否已經(jīng)釋放鎖。當(dāng)一個(gè)線程試圖獲取鎖時(shí),從隊(duì)列尾節(jié)點(diǎn)作為前序節(jié)點(diǎn),循環(huán)判斷所有的前序節(jié)點(diǎn)是否已經(jīng)成功釋放鎖。

大數(shù)據(jù)培訓(xùn),云培訓(xùn),數(shù)據(jù)挖掘培訓(xùn),云計(jì)算培訓(xùn),高端軟件開發(fā)培訓(xùn),項(xiàng)目經(jīng)理培訓(xùn)

如上圖所示,StampedLockd源碼中的WNote就是等待鏈表隊(duì)列,每一個(gè)WNode標(biāo)識(shí)一個(gè)等待線程,whead為CLH隊(duì)列頭,wtail為CLH隊(duì)列尾,state為鎖的狀態(tài)。long型即64位,倒數(shù)第八位標(biāo)識(shí)寫鎖狀態(tài),如果為1,標(biāo)識(shí)寫鎖占用!下面圍繞這個(gè)state來講述鎖操作。

首先是常量標(biāo)識(shí):

WBIT=1000 0000(即-128)

RBIT =0111 1111(即127) 

SBIT =1000 0000(后7位表示當(dāng)前正在讀取的線程數(shù)量,清0)

1.樂觀讀

大數(shù)據(jù)培訓(xùn),云培訓(xùn),數(shù)據(jù)挖掘培訓(xùn),云計(jì)算培訓(xùn),高端軟件開發(fā)培訓(xùn),項(xiàng)目經(jīng)理培訓(xùn)

tryOptimisticRead():如果當(dāng)前沒有寫鎖占用,返回state(后7位清0,即清0讀線程數(shù)),如果有寫鎖,返回0,即失敗。

2.校驗(yàn)stamp

大數(shù)據(jù)培訓(xùn),云培訓(xùn),數(shù)據(jù)挖掘培訓(xùn),云計(jì)算培訓(xùn),高端軟件開發(fā)培訓(xùn),項(xiàng)目經(jīng)理培訓(xùn)

校驗(yàn)這個(gè)戳是否有效validate():比較當(dāng)前stamp和發(fā)生樂觀鎖得到的stamp比較,不一致則失敗。

3.悲觀讀

大數(shù)據(jù)培訓(xùn),云培訓(xùn),數(shù)據(jù)挖掘培訓(xùn),云計(jì)算培訓(xùn),高端軟件開發(fā)培訓(xùn),項(xiàng)目經(jīng)理培訓(xùn)

樂觀鎖失敗后鎖升級(jí)為readLock():嘗試state+1,用于統(tǒng)計(jì)讀線程的數(shù)量,如果失敗,進(jìn)入acquireRead()進(jìn)行自旋,通過CAS獲取鎖。如果自旋失敗,入CLH隊(duì)列,然后再自旋,如果成功獲得讀鎖,激活cowait隊(duì)列中的讀線程Unsafe.unpark(),最終依然失敗,Unsafe().park()掛起當(dāng)前線程。

4.排它寫

大數(shù)據(jù)培訓(xùn),云培訓(xùn),數(shù)據(jù)挖掘培訓(xùn),云計(jì)算培訓(xùn),高端軟件開發(fā)培訓(xùn),項(xiàng)目經(jīng)理培訓(xùn)

writeLock():典型的cas操作,如果STATE等于s,設(shè)置寫鎖位為1(s+WBIT)。acquireWrite跟acquireRead邏輯類似,先自旋嘗試、加入等待隊(duì)列、直至最終Unsafe.park()掛起線程。

5.釋放鎖

大數(shù)據(jù)培訓(xùn),云培訓(xùn),數(shù)據(jù)挖掘培訓(xùn),云計(jì)算培訓(xùn),高端軟件開發(fā)培訓(xùn),項(xiàng)目經(jīng)理培訓(xùn)

unlockWrite():釋放鎖與加鎖動(dòng)作相反。將寫標(biāo)記位清零,如果state溢出,則退回到初始值;

性能和建議JDK8之后才有,當(dāng)高并發(fā)下且讀遠(yuǎn)大于寫時(shí),由于可以樂觀讀,性能極高!

5.總結(jié)

4種鎖,最穩(wěn)定是內(nèi)置synchronized鎖(并不是完全被替代),當(dāng)并發(fā)量大且讀遠(yuǎn)大于寫的情況下最快的的是StampedLock鎖(樂觀讀。近似于無鎖)。建議大家采用。

====================

參考:《JAVA高并發(fā)程序設(shè)計(jì)》

------------------ 本人實(shí)力有限,理解有誤之處大家一定要提出來,多多發(fā)言討論。共同提高! ------------------

http://www.cnblogs.com/dennyzhangdd/p/6925473.html