1. 程式人生 > >ReentrantLock和condition原始碼淺析(二)

ReentrantLock和condition原始碼淺析(二)

 轉載請註明出處。。。

接著上一篇的ReentrantLock和condition原始碼淺析(一),這篇圍繞著condition

一、condition的介紹

在這裡為了作對比,引入Object類的兩個方法,notify和wait方法,這兩個方法作用,估計都很清楚,就是一個具有喚醒執行緒,另一個具有讓執行緒等待的功能,適用場景,像類似生產者,消費者,那樣。

這兩個方法的注意點,在於必須放在同步塊中執行,具體原因可自行百度或谷歌。執行wait方法後,執行緒會阻塞,並釋放同步程式碼塊的鎖(sleep方法會持有鎖),notify的方法執行,會喚醒某個執行緒,但是如果有多個執行緒執行wait方法阻塞,notify的執行只會喚醒其中某個執行緒,並不能指定執行緒喚醒,這時要使用notifyAll才能達到喚醒所有阻塞執行緒。這樣確實有點麻煩,而condition的引入就是為了解決只喚醒執行阻塞的執行緒。它具有超時作用,即超過某段時間,即會自動喚醒,不會造成一直阻塞。常用的阻塞佇列就是這個類實現的。

使用注意點,await和signal必須要在lock方法後執行,如果不執行lock方法就執行await或signal,會出現異常的,具體原因,稍後分析。

 二、condition的await方法

在我們使用該方法時,首先會獲取Lock介面的一個實現類,然後呼叫newCondition類方法,本文以ReentrantLock為例。使用方法如下

1 Lock lock = new ReentrantLock();
2 
3 Condition empty = lock.newCondition();
4 
5 // 阻塞執行緒,並釋放鎖
6 empty.await();
7 
8
// 喚醒前面阻塞的執行緒 9 empty.signal();

 

 這裡先從await方法(不帶引數)開始,程式碼如下

public final void await() throws InterruptedException {
            if (Thread.interrupted())
                throw new InterruptedException();
            // 將當前執行緒包裝成一個Node節點,並節點狀態為condition,值為-2
            Node node = addConditionWaiter();
            
// 釋放當前執行緒持有的所有鎖 long savedState = fullyRelease(node); int interruptMode = 0; // 判斷當前執行緒是否在同步佇列中,即在head->tail佇列中,如果不在,那就是還在等待佇列中,阻塞當前執行緒。 while (!isOnSyncQueue(node)) { LockSupport.park(this); if ((interruptMode = checkInterruptWhileWaiting(node)) != 0) break; } // 噹噹前執行緒執行了signal方法會經過這個,即重新將當前執行緒加入同步佇列中 if (acquireQueued(node, savedState) && interruptMode != THROW_IE) interruptMode = REINTERRUPT; if (node.nextWaiter != null) // clean up if cancelled unlinkCancelledWaiters(); if (interruptMode != 0) reportInterruptAfterWait(interruptMode); }

 

前面也說了該方法一定在lock方法內部執行,不然就會拋異常。具體程式碼在fullRelease(node)方法內,程式碼如下

 1 final long fullyRelease(Node node) {
 2         boolean failed = true;
 3         try {
 4             // 獲取當前執行緒狀態值,即持有了幾個鎖
 5             long savedState = getState();
 6            // 釋放鎖,最後最執行到ReentrantLock的tryRelease()方法,該段程式碼會判斷當前執行緒是與持有鎖的執行緒是同一個執行緒,如果不是,則拋異常
 7             if (release(savedState)) {
 8                 failed = false;
 9                 return savedState;
10             } else {
11                 throw new IllegalMonitorStateException();
12             }
13         } finally {
14             if (failed)
15                 // 拋異常了,則將此節點狀態改為canceled,等待從佇列中移除
16                 node.waitStatus = Node.CANCELLED;
17         }
18     }

 

該方法的執行,有這幾個步驟

1、將當前執行緒包裝成一個node節點,且狀態節點為condition,值為-2。

2、釋放當前執行緒持有的所有鎖,讓下一個執行緒能獲取鎖。

3、如果條件滿足,則阻塞該執行緒,等待被喚醒

4、若被喚醒,則嘗試加入該節點到同步佇列中,直到獲取鎖。

至於該方法的幾個其他的過載方法,這裡不過多敘述

不一樣的地方就是改造了while()迴圈內部程式碼,重點是使用了LockSupport.parkNanos方法能保證在規定時間,該執行緒會被喚醒。其他判斷就是一些當前執行緒有沒有被中斷,有沒有達到等待時間等。

三、condition的signal方法

 該方法用來喚醒執行await方法被阻塞的執行緒,具體程式碼如下

 1 public final void signal() {
 2             // 與await方法一樣,如果不在lock方法內執行,則也會拋異常
 3             if (!isHeldExclusively())
 4                 throw new IllegalMonitorStateException();
 5             Node first = firstWaiter;
 6             if (first != null)
 7                 // 喚醒等待佇列中執行緒
 8                 doSignal(first);
 9 }
10 
11 private void doSignal(Node first) {
12             do {
13                 if ( (firstWaiter = first.nextWaiter) == null)
14                     lastWaiter = null;
15                 first.nextWaiter = null;
16             } while (!transferForSignal(first) &&
17                      (first = firstWaiter) != null);
18 }
19 
20 final boolean transferForSignal(Node node) {
21         /*
22          * If cannot change waitStatus, the node has been cancelled. 如果改變node節點狀態失敗,即該節點被取消了
23          */
24         if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
25             return false;
26         // 將該節點加入同步佇列中,即head->tail佇列中
27         Node p = enq(node);
28         int ws = p.waitStatus;
29         // 如果節點被取消,或更改狀態失敗,則喚醒被阻塞的執行緒
30         if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
31             LockSupport.unpark(node.thread);
32         return true;
33     }
34     

 

 或許有的人會有疑問,執行await方法,有執行LockSupport.park方法來阻塞執行緒,但是執行signal方法順利的話,沒有程式碼執行LockSupport.unpark方法,那執行緒豈不是還一直在阻塞中?

其實呢,signal起的作用並不是直接喚醒執行緒,它的作用是把阻塞的執行緒移到同步佇列中,在上一篇中ReentrantLock和condition原始碼淺析(一) 博文中有介紹release方法,該方法內部有一個執行unpark方法,

它會去不斷的釋放滿足條件的並被阻塞的執行緒。

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

 以上內容就是我堆condition的瞭解,若有錯誤或不足之處,還望指正,謝謝!