ReentrantLock和condition源碼淺析(一)

 轉載請註明出處。。。。。java

1、介紹

你們都知道,在java中若是要對一段代碼作線程安全操做,都用到了鎖,固然鎖的實現不少,用的比較多的是sysnchronize和reentrantLock,前者是java裏的一個關鍵字,後者是一個java類。這二者的大體區別,在這裏羅列下node

相同點:安全

       一、都能保證了線程安全性jvm

       二、都支持鎖的重入函數

不一樣點:ui

      一、synchronized適用於不是很激烈的狀況,reentranLock適用於比較競爭激烈的狀況this

      二、Synchronized是jvm層面實現的鎖機制,而reentranLock是java代碼層面實現的鎖機制。spa

      三、Reentranlock比synchronized多了鎖投票,定時鎖,中斷鎖等機制線程

      四、synchronized是隱式獲取鎖和釋放鎖,不須要代碼手動獲取釋放,Reentranlock爲顯示獲取鎖和釋放鎖,必需要手動代碼獲取釋放code

要了解reentranlock,那確定先得會用它,下面經過一個例子來了解它的加鎖和釋放鎖過程

2、demo

 1 public class Demo {  2 
 3     private static int count = 0;  4 
 5     public static void main(String[] args) throws InterruptedException {  6         ExecutorService executorService = Executors.newFixedThreadPool(15);  7         for (int i = 0; i < 500; i++){  8             executorService.execute(() -> {  9  add(); 10  }); 11  } 12         Thread.sleep(1000); 13  System.out.println(count); 14  } 15 
16     private static int add(){ 17         return ++count; 18  } 19 }

 

 上述代碼,安裝預期結果 那確定是500,可是真的是500嗎?結果以下

結果很顯然,它是小於500的,把這段代碼用鎖保證結果和預期結果一致。代碼以下

 1 public class Demo {  2 
 3     private static int count = 0;  4 
 5     private static Lock lock = new ReentrantLock();  6 
 7     public static void main(String[] args) throws InterruptedException {  8         ExecutorService executorService = Executors.newFixedThreadPool(15);  9         for (int i = 0; i < 500; i++){ 10             executorService.execute(() -> { 11  add(); 12  }); 13  } 14         Thread.sleep(1000); 15  System.out.println(count); 16  } 17 
18     private static int add(){ 19  lock.lock(); 20         try { 21             return ++count; 22         }finally { 23  lock.unlock(); 24  } 25 
26  } 27 }

結果,和預期一致。

那它是怎麼保證線程安全性的呢。往下看

3、ReentrantLock分析

 先來了解這個類的大體結構

紅框圈中的三個類,其中Sync是一個抽象類,另外兩個是它的子類,Sync又繼承了AQS類,因此它也有鎖的操做可能性。

FairSync是一個公平鎖,NonFairSync是一個非公平鎖,它們雖然繼承了同一個類,但實現上有所不一樣,

一、非公平鎖獲取鎖的過程

進入lock方法

而sync 是ReentrantLock的一個字段,它在該類的構造函數中初始化,它有兩個構造函數,sync默認爲非公平鎖實現,

當sync調用了lock方法,也就是調用NonFairSync類的lock方法,繼續看下去,下圖爲該類的結構

 

lock大體步驟爲,先去試着改變state的值,若是改變成功,則state值就變爲1了,返回true,失敗返回false,先來解釋下compareAndSetState方法的做用

它有兩個參數,第一個是指望值,第二個是要更新的值,若是內存中state值和指望值相等,則將內存值變爲更新值,這是交換成功的標誌。若是不相等,那確定是false。這個方法其實就是CAS,同時它也是線程安全的,具體實現,這裏不做討論。

這裏也是獲取鎖成功的標誌,當返回true,則將獲取鎖的線程置爲當前線程,同時state值改變了,若是下一個線程進入,那麼該方法確定是返回false。那麼獲取鎖失敗的線程就會進入acquire方法。這個方法其實就是AQS的方法,代碼以下,能夠看到它又調用了tryAcquire方法,而這個方法的實現就是上一個圖的nonFairTryAcquire方法,

1 final boolean nonfairTryAcquire(int acquires) { 2             // 獲取當前線程
 3             final Thread current = Thread.currentThread(); 4             int c = getState(); 5             // 若是狀態值不爲0,則進一步去獲取鎖
 6             if (c == 0) { 7                 if (compareAndSetState(0, acquires)) { 8                     // 獲取鎖成功,將鎖置給當前線程
 9 setExclusiveOwnerThread(current); 10                     return true; 11 } 12             }// 若是相等,則代表爲鎖的重入
13             else if (current == getExclusiveOwnerThread()) { 14                 int nextc = c + acquires; 15                 if (nextc < 0) // overflow
16                     throw new Error("Maximum lock count exceeded"); 17 setState(nextc); 18                 return true; 19 } 20            // 只有獲取鎖失敗纔會返回false
21             return false; 22         }

 

 當上面返回false時,又會相繼執行addWaiter和acquireQueued方法,其中addWaiter方法主要是將獲取鎖失敗的線程包裝成一個Node節點,插入一個隊列中,注意頭結點不是該節點,而是new了一個新的node節點,它的狀態值爲0,而後返回該節點。

具體代碼不作分析,下面看acquireQueued方法

 1  final boolean acquireQueued(final Node node, int arg) {  2         boolean failed = true;  3         try {  4             boolean interrupted = false;  5             // 相似while(true),做無限循環做用
 6             for (;;) {  7                 // 獲取插入的node節點的前一個節點
 8                 final Node p = node.predecessor();  9                 // 若是前繼節點爲head節點而且獲取鎖成功,則跳出無限循環,執行相應業務代碼
10                 if (p == head && tryAcquire(arg)) { 11  setHead(node);// 頭結點被改變,改變同時其狀態也被改變了,節點線程也爲空 12                     p.next = null; // help GC
13                     failed = false; 14                     return interrupted; 15  } 16                 // 前繼節點不是頭結點或獲取鎖失敗
17                 if (shouldParkAfterFailedAcquire(p, node) &&
18  parkAndCheckInterrupt()) 19                     interrupted = true; 20  } 21         } finally { 22             // 防止代碼運行過程當中,線程忽然被中斷,中斷則將該節點置爲取消狀態
23             if (failed) 24  cancelAcquire(node); 25  } 26     }

 

其中shouldParkAfterFailedAcquire方法作了這兩件事,

一、若是p節點前有狀態爲cancel的節點,則將這些取消的節點放棄掉,簡單來講就是排除取消的節點

二、將p節點狀態置爲signal狀態。等待下一次進入該方法可能會被掛起

方法parkAndCheckInterrupt,在shouldParkAfterFailedAcquire返回true的時候,線程會被掛起。、

以上就是獲取鎖的過程,步驟以下

一、獲取鎖成功,則將改變state值,並將鎖的擁有者置爲當前線程

二、獲取鎖失敗,則進入同步隊列中,直到獲取鎖成功或當前線程被外因給中斷,獲取鎖的過程當中,有的線程可能會被掛起。

二、非公平鎖釋放鎖的過程

爲了避免顯得過於囉嗦,下面只列出核心代碼

上述代碼只有獲取鎖的線程調用了unlock方法,纔會去修改state值,當state值爲0時其餘線程又能夠獲取鎖,看到這,或許有的小夥伴迷糊了,上面不是介紹說在獲取鎖的過程當中,有的線程會被掛起,那若是掛起的線程node節點前繼剛好是頭結點,那豈不是運行不了?,莫慌,往下看。當state值置爲0時,該方法會返回true,以後會執行下面方法。

 重點方法在unparkSuccessor方法上,看if(h != null && h.waitStatus !=0) ,爲何要加這個判斷呢,由於若是有多個線程在獲取鎖,不管是獲取失敗,仍是獲取成功head節點的狀態值都被改變(setHead()和shouldParkAfterFailedAcquire()方法會去改變head節點狀態)。即不爲0,若是爲0,那麼就說明就沒有線程被掛起,天然就不用去釋放這些線程。加這個判斷,爲了減小無用操做。重點來了,unparkSuccessor方法,代碼以下

private void unparkSuccessor(Node node) { // 將node結點狀態置爲0
        int ws = node.waitStatus; if (ws < 0) compareAndSetWaitStatus(node, ws, 0); /* * 若是node結點下一個節點爲null或被取消則進入下面的for循環 * 下面的for循環從尾節點往前尋找沒有取消的節點 ,直至最靠近node節點,即node節點下一個狀態小於等於0的節點 * 在這裏node節點就是頭結點, */ Node s = node.next; if (s == null || s.waitStatus > 0) { s = null; for (Node t = tail; t != null && t != node; t = t.prev) if (t.waitStatus <= 0) s = t; } // 找到了該節點,釋放該節點的線程
        if (s != null) LockSupport.unpark(s.thread); } 

 

或許看到這更迷糊了,它釋放鎖怎麼能肯定釋放的就是那個被掛起的線程呢,這個呢,確實肯定不了,可是若是釋放前繼節點爲頭結點的線程,那麼在後續獲取鎖的過程當中,該線程確定能獲取到鎖(由於這段代碼是前一個線程釋放鎖的操做代碼,因此下一個線程確定能獲取到鎖),至此又一輪循環。

在這裏,我對那個爲啥從尾節點向前遍歷也不清楚,若是有清楚的小夥伴,還請評論下方留言,謝謝!

以上就是非公平鎖的釋放操做。

三、公平鎖的獲取鎖過程

該種鎖和非公平鎖的不一樣之處,就是這種鎖必定得按照順序來獲取或,不能前一個線程釋放了鎖 ,而後誰搶到了就算誰的。

先來看下這種獲取鎖的代碼

 1 protected final boolean tryAcquire(int acquires) {  2             final Thread current = Thread.currentThread();  3             int c = getState();  4             if (c == 0) {  5                 if (!hasQueuedPredecessors() &&
 6                     compareAndSetState(0, acquires)) {  7  setExclusiveOwnerThread(current);  8                     return true;  9  } 10  } 11             else if (current == getExclusiveOwnerThread()) { 12                 int nextc = c + acquires; 13                 if (nextc < 0) 14                     throw new Error("Maximum lock count exceeded"); 15  setState(nextc); 16                 return true; 17  } 18             return false; 19         }

 

和非公平鎖的不一樣點是在前者線程釋放鎖後(即state值爲0),非公平鎖是誰搶到鎖,鎖就是誰的,可是公平鎖不同,獲取鎖的線程會先去判斷同步隊列中有沒有其餘線程,若是沒有,再去試着改變state值,若是改變成功則獲取鎖成功,它不容許沒進入同步隊列中的線程(此時同步隊列中已有等待的線程,若是沒有,那就是直接搶)搶佔鎖。下面看下hasQueuedPrdecessor(),代碼以下

 1 public final boolean hasQueuedPredecessors() {  2         // The correctness of this depends on head being initialized  3         // before tail and on head.next being accurate if the current  4         // thread is first in queue.
 5         Node t = tail; // Read fields in reverse initialization order
 6         Node h = head;  7  Node s;  8         return h != t &&
 9             ((s = h.next) == null || s.thread != Thread.currentThread()); 10     }

 

代碼不復雜,就是判斷同步隊列中有沒有等待的線程,且等待的線程不是當前線程,有則返回true,沒有則返回false。

至於公平鎖的釋放操做,和非公平鎖一致。這裏不過多敘述。

獲取公平鎖操做

一、先判斷同步隊列中有沒有等待的線程。

二、有則放棄鎖的爭奪,進入同步隊列排好隊,沒有則搶佔鎖

 ----------------------------------------------------------------------------------------------------華麗的分界線---------------------------------------------------------------------------------------------------------------------------------

 原本想繼續寫condition,但好像篇幅有點囉嗦,就放在下一篇。

以上就是個人我的看法,若是不足或錯誤之處,還請指教,謝謝!

相關文章
相關標籤/搜索