ReentrantLock和condition源碼淺析(二)

 轉載請註明出處。。。html

接着上一篇的ReentrantLock和condition源碼淺析(一),這篇圍繞着conditionnode

1、condition的介紹

在這裏爲了做對比,引入Object類的兩個方法,notify和wait方法,這兩個方法做用,估計都很清楚,就是一個具備喚醒線程,另外一個具備讓線程等待的功能,適用場景,像相似生產者,消費者,那樣。post

這兩個方法的注意點,在於必須放在同步塊中執行,具體緣由可自行百度或谷歌。執行wait方法後,線程會阻塞,並釋放同步代碼塊的鎖(sleep方法會持有鎖),notify的方法執行,會喚醒某個線程,可是若是有多個線程執行wait方法阻塞,notify的執行只會喚醒其中某個線程,並不能指定線程喚醒,這時要使用notifyAll才能達到喚醒全部阻塞線程。這樣確實有點麻煩,而condition的引入就是爲了解決只喚醒執行阻塞的線程。它具備超時做用,即超過某段時間,即會自動喚醒,不會形成一直阻塞。經常使用的阻塞隊列就是這個類實現的。ui

使用注意點,await和signal必需要在lock方法後執行,若是不執行lock方法就執行await或signal,會出現異常的,具體緣由,稍後分析。this

 2、condition的await方法

在咱們使用該方法時,首先會獲取Lock接口的一個實現類,而後調用newCondition類方法,本文以ReentrantLock爲例。使用方法以下spa

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)方法內,代碼以下code

 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     }

 

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

一、將當前線程包裝成一個node節點,且狀態節點爲condition,值爲-2。blog

二、釋放當前線程持有的全部鎖,讓下一個線程能獲取鎖。

三、若是條件知足,則阻塞該線程,等待被喚醒

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

至於該方法的幾個其餘的重載方法,這裏不過多敘述

不同的地方就是改造了while()循環內部代碼,重點是使用了LockSupport.parkNanos方法能保證在規定時間,該線程會被喚醒。其餘判斷就是一些當前線程有沒有被中斷,有沒有達到等待時間等。

3、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的瞭解,如有錯誤或不足之處,還望指正,謝謝!

相關文章
相關標籤/搜索