轉自:http://www.importnew.com/9281.html
在java.util.concurrent
包中,有兩個很特殊的工具類,Condition
和ReentrantLock
,使用過的人都知道,ReentrantLock
(重入鎖)是jdk的concurrent
包提供的一種獨佔鎖的實現。它繼承自Dong
Lea的 AbstractQueuedSynchronizer
(同步器),確切的說是ReentrantLock
的一個內部類繼承了AbstractQueuedSynchronizer
,ReentrantLock
只不過是代理了該類的一些方法,可能有人會問爲什麼要使用內部類在包裝一層?
我想是安全的關係,因爲AbstractQueuedSynchronizer
中有很多方法,還實現了共享鎖,Condition
(稍候再細說)等功能,如果直接使ReentrantLock
繼承它,則很容易出現AbstractQueuedSynchronizer
中的API被無用的情況。
言歸正傳,今天,我們討論下Condition
工具類的實現。
ReentrantLock
和Condition
的使用方式通常是這樣的:
publicstatic void main(String[] args) {
finalReentrantLock reentrantLock = newReentrantLock();
finalCondition condition = reentrantLock.newCondition();
Thread thread = newThread((Runnable) () -> {
try{
reentrantLock.lock();
System.out.println("我要等一個新信號"+ this);
condition.wait();
}
catch(InterruptedException e) {
e.printStackTrace();
}
System.out.println("拿到一個信號!!"+ this);
reentrantLock.unlock();
},"waitThread1");
thread.start();
Thread thread1 = newThread((Runnable) () -> {
reentrantLock.lock();
System.out.println("我拿到鎖了");
try{
Thread.sleep(3000);
}
catch(InterruptedException e) {
e.printStackTrace();
}
condition.signalAll();
System.out.println("我發了一個信號!!");
reentrantLock.unlock();
},"signalThread");
thread1.start();
}
運行後,結果如下:
我要等一個新信號lock.ReentrantLockTest$1@a62fc3
我拿到鎖了
我發了一個信號!!
拿到一個信號!!
Condition
的執行方式,是當在線程1中調用await
方法後,線程1將釋放鎖,並且將自己沉睡,等待喚醒,
線程2獲取到鎖後,開始做事,完畢後,調用Condition
的signal
方法,喚醒線程1,線程1恢復執行。
以上說明Condition
是一個多線程間協調通信的工具類,使得某個,或者某些線程一起等待某個條件(Condition),只有當該條件具備( signal
或者 signalAll
方法被帶調用)時
,這些等待線程纔會被喚醒,從而重新爭奪鎖。
那,它是怎麼實現的呢?
首先還是要明白,reentrantLock.newCondition()
返回的是Condition
的一個實現,該類在AbstractQueuedSynchronizer
中被實現,叫做newCondition()
publicCondition newCondition() { returnsync.newCondition(); }
它可以訪問AbstractQueuedSynchronizer
中的方法和其餘內部類(AbstractQueuedSynchronizer
是個抽象類,至於他怎麼能訪問,這裏有個很奇妙的點,後面我專門用demo說明
)
現在,我們一起來看下Condition
類的實現,還是從上面的demo入手,
爲了方便書寫,我將AbstractQueuedSynchronizer
縮寫爲AQS
當await
被調用時,代碼如下:
publicfinal void await() throwsInterruptedException {
if(Thread.interrupted())
thrownew InterruptedException();
Node node = addConditionWaiter(); // 將當前線程包裝下後,
// 添加到Condition自己維護的一個鏈表中。
intsavedState = fullyRelease(node);// 釋放當前線程佔有的鎖,從demo中看到,
// 調用await前,當前線程是佔有鎖的
intinterruptMode = 0;
while(!isOnSyncQueue(node)) {// 釋放完畢後,遍歷AQS的隊列,看當前節點是否在隊列中,
// 不在 說明它還沒有競爭鎖的資格,所以繼續將自己沉睡。
// 直到它被加入到隊列中,聰明的你可能猜到了,
// 沒有錯,在singal的時候加入不就可以了?
LockSupport.park(this);
if((interruptMode = checkInterruptWhileWaiting(node)) != 0)
break;
}
// 被喚醒後,重新開始正式競爭鎖,同樣,如果競爭不到還是會將自己沉睡,等待喚醒重新開始競爭。
if(acquireQueued(node, savedState) && interruptMode != THROW_IE)
interruptMode = REINTERRUPT;
if(node.nextWaiter != null)
unlinkCancelledWaiters();
if(interruptMode != 0)
reportInterruptAfterWait(interruptMode);
}
publicfinal void signal() {
if(!isHeldExclusively())
thrownew IllegalMonitorStateException();
Node first = firstWaiter; // firstWaiter爲condition自己維護的一個鏈表的頭結點,
// 取出第一個節點後開始喚醒操作
if(first != null)
doSignal(first);
}
Condition
內部維護了等待隊列的頭結點和尾節點,該隊列的作用是存放等待signal信號的線程,該線程被封裝爲Node
節點後存放於此。publicclass ConditionObject implementsCondition, java.io.Serializable {
privatestatic final long serialVersionUID = 1173984872572414699L;
/** First node of condition queue. */
privatetransient Node firstWaiter;
/** Last node of condition queue. */
privatetransient Node lastWaiter;
而Condition自己也維護了一個隊列,該隊列的作用是維護一個等待signal信號的隊列,兩個隊列的作用是不同,事實上,每個線程也僅僅會同時存在以上兩個隊列中的一個,流程是這樣的:
- 線程1調用
reentrantLock.lock
時,線程被加入到AQS的等待隊列中。 - 線程1調用
await
方法被調用時,該線程從AQS中移除,對應操作是鎖的釋放。 - 接着馬上被加入到
Condition
的等待隊列中,以爲着該線程需要signal
信號。 - 線程2,因爲線程1釋放鎖的關係,被喚醒,並判斷可以獲取鎖,於是線程2獲取鎖,並被加入到AQS的等待隊列中。
- 線程2調用
signal
方法,這個時候Condition
的等待隊列中只有線程1一個節點,於是它被取出來,並被加入到AQS的等待隊列中。 注意,這個時候,線程1 並沒有被喚醒。 signal
方法執行完畢,線程2調用reentrantLock.unLock()
方法,釋放鎖。這個時候因爲AQS中只有線程1,於是,AQS釋放鎖後按從頭到尾的順序喚醒線程時,線程1被喚醒,於是線程1回覆執行。- 直到釋放所整個過程執行完畢。
可以看到,整個協作過程是靠結點在AQS的等待隊列和Condition
的等待隊列中來回移動實現的,Condition
作爲一個條件類,很好的自己維護了一個等待信號的隊列,並在適時的時候將結點加入到AQS的等待隊列中來實現的喚醒操作。
看到這裏,signal方法的代碼應該不難理解了。
取出頭結點,然後doSignal
publicfinal void signal() {
if(!isHeldExclusively()) {
thrownew IllegalMonitorStateException();
}
Node first = firstWaiter;
if(first != null) {
doSignal(first);
}
}
privatevoid doSignal(Node first) {
do{
if((firstWaiter = first.nextWaiter) == null)// 修改頭結點,完成舊頭結點的移出工作
lastWaiter = null;
first.nextWaiter = null;
}while(!transferForSignal(first) && // 將老的頭結點,加入到AQS的等待隊列中
(first = firstWaiter) != null);
}
finalboolean transferForSignal(Node node) {
/*
* If cannot change waitStatus, the node has been cancelled.
*/
if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
return false;
/*
* Splice onto queue and try to set waitStatus of predecessor to
* indicate that thread is (probably) waiting. If cancelled or attempt
* to set waitStatus fails, wake up to resync (in which case the
* waitStatus can be transiently and harmlessly wrong).
*/
Node p = enq(node);
intws = p.waitStatus;
// 如果該結點的狀態爲cancel 或者修改waitStatus失敗,則直接喚醒。
if(ws > 0|| !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
LockSupport.unpark(node.thread);
returntrue;
}
ws
> 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL)
這個判斷是不會爲true
的,所以,不會在這個時候喚醒該線程。
只有到發送signal
信號的線程調用reentrantLock.unlock()
後因爲它已經被加到AQS的等待隊列中,所以纔會被喚醒。