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的了解,若有錯誤或不足之處,還望指正,謝謝!
ReentrantLock和condition源碼淺析(二)