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

ReentrantLock和condition源碼淺析(二)

log shel itl vat monitors exception 定時 移除 作用

轉載請註明出處。。。

接著上一篇的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的了解,若有錯誤或不足之處,還望指正,謝謝!

ReentrantLock和condition源碼淺析(二)