ReentrantLock類有一個方法newCondition用來生成這個鎖對象的一個條件(ConditionObject)對象,它實現了Condition接口。
Condition提供了線程通訊的一套機制await和signal等線程間進行通訊的方法。。
1、適用場景
? ? ?當某線程獲取了鎖對象,但由于某些條件沒有滿足,須要在這個條件上等待,直到條件滿足才可以往下繼續運行時。就須要用到條件鎖。
? ? ?這樣的情況下,線程主動在某條件上堵塞,當其他線程發現條件發生變化時,就能夠喚醒堵塞在此條件上的線程。
2、使用演示樣例
? ? ?以下是來自JDK的一段演示樣例代碼,須要先獲得某個鎖對象之后,才干調用這個鎖的條件對象進行堵塞。
? ? ?
class BoundedBuffer {final Lock lock = new ReentrantLock();final Condition notFull = lock.newCondition(); final Condition notEmpty = lock.newCondition(); final Object[] items = new Object[100];int putptr, takeptr, count;public void put(Object x) throws InterruptedException {lock.lock(); try {while (count == items.length)notFull.await();items[putptr] = x;if (++putptr == items.length) putptr = 0;++count;notEmpty.signal();} finally {lock.unlock();}}public Object take() throws InterruptedException {lock.lock();try {while (count == 0)notEmpty.await();Object x = items[takeptr];if (++takeptr == items.length) takeptr = 0;--count;notFull.signal();return x;} finally {lock.unlock();}}}
注意上面的代碼,先是通過lock.lock獲得了鎖對象,然后發現條件不滿足時(count==items.length),緩存已滿,無法繼續往里面寫入數據,這時候就調用條件對象notFull.await()進行堵塞。
假設條件滿足,就會往緩存中寫入數據,同一時候通知等待緩存非空的線程,notEmpty.signal.
這樣就實現了讀線程和寫線程之間的通訊
3、線程堵塞對鎖的影響
? ? ?上面的樣例中。線程是先獲得了鎖對象之后。然后調用notFull.await進行的線程堵塞。在這樣的情況下,擁有鎖的線程進入堵塞,是否可能會造成死鎖。
? ? ?
? ? ?答案當然是否定的。
由于線程在調用條件對象的await方法中,首先會釋放當前的鎖,然后才讓自己進入堵塞狀態,等待喚醒。
4、線程的條件等待、喚醒與鎖對象的關系
? ? ?在ReentrantLock解析中說過。AbstractQueuedSynchronizer的內部維護了一個隊列,等待該鎖的線程是在這個隊列中。類似的,ConditionObject內部也是維護了一個隊列,等待該條件的線程也構成了一個隊列。
? ? ?當現成調用await進入堵塞時。便會增加到ConditionObject內部的等待隊列中。
注意,這里是自動進入堵塞。除非被其他線程喚醒或者被中斷,否則線程將一直堵塞下去。
? ? ?當其他線程調用signal喚醒堵塞的線程時,便把等待隊列中的第一個節點從隊列中移除,同一時候把節點增加到AbstractQueuedSynchronizer 鎖對象內的等待隊列中。為什么是進入到鎖的等待隊列中?由于線程被喚醒之后,并不意味著就能立馬運行。
此時,其他線程有可能正好擁有這個鎖,前面也已經有現成在等待這個鎖,所以被喚醒的線程須要進入鎖的等待隊列中,在前面的線程運行完畢后,才干繼續興許的操作。
? ? ?可參考下圖
? ? ?
? ? ?
5、線程能否同一時候處于條件對象的等待隊列中和鎖對象的等待隊列中
? ? ?不能。
線程僅僅有調用條件對象的await方法,才干進入這個條件對象的等待隊列中。而線程在調用await方法的前提是線程已經獲取了鎖,所以線程是在擁有鎖的狀態下進入條件對象的等待隊列的。擁有鎖的線程也就是正在執行的線程,是不在鎖對象的等待隊列中的。
? ? ?僅僅有當一個線程試著獲取鎖的時候。而這個鎖正好又由其他線程占領的時候。線程才會進入鎖的等待隊列中,等待擁有鎖的線程運行完畢。釋放鎖的時候被喚醒。
6、實現原理
? ? ?相關代碼在AbstractQueuedSynchronizer的內部類ConditionObject中能夠看到。
? ? ?ConditionObject有兩個屬性firstWaiter和lastWaiter,分別指向的是這個條件對象等待隊列的頭和尾。
? ? ?隊列的各個節點都是Node(AbstractQueuedSynchronizer的內部類)對象,通過Node對象的nextWaiter之間進行向下傳遞,所以,條件對象的等待隊列是一個單向鏈表。
以下是await的源碼
?? ? ??public?final?void?await?()?throws?InterruptedException {
????????????if?(Thread.interrupted())
????????????????throw?new?InterruptedException();
??????????? Node node = addConditionWaiter();
????????????int?savedState = fullyRelease(node);
????????????int?interruptMode = 0;
????????????while?(!isOnSyncQueue(node)) {
??????????????? 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);
??????? }
首先是調用addConditionWaiter把當前線程增加到條件對象的等待隊列中,然后fullyRelease來釋放鎖,然后通過isOnSyncQueue來檢查當前線程節點是否在鎖對象的等待隊列中。
為什么要做這個檢查?由于線程被signal喚醒的時候,是首先增加到鎖對象的等待隊列中的。
假設沒有在鎖對象的等待隊列中,那么說明事件還沒有發生(也就是沒有signal方法沒有被調用)。所以線程須要堵塞來等待被喚醒。
在addConditionWaiter方法中完畢了等待隊列的構建過程,代碼例如以下
private?Node?addConditionWaiter() {
??????????? Node t =?lastWaiter;
????????????// If lastWaiter is cancelled, clean out.
????????????if?(t !=?null?&& t.waitStatus?!= Node.?CONDITION) {
??????????????? unlinkCancelledWaiters();
??????????????? t =?lastWaiter;
??????????? }
??????????? Node node =?new?Node(Thread.currentThread(), Node.?CONDITION);
????????????if?(t ==?null?)
????????????????firstWaiter?= node;
????????????else
??????????????? t.?nextWaiter?= node;
????????????lastWaiter?= node;
????????????return?node;
??????? }
線程增加隊列的順序與增加的時間一致,剛增加的線程是在隊列的最后面。
以下來看線程的喚醒
?public?final?void?signal() {
????????????if?(!isHeldExclusively())
????????????????throw?new?IllegalMonitorStateException();
??????????? Node first =?firstWaiter;
????????????if?(first !=?null)
??????????????? doSignal(first);
??????? }
喚醒操作實際上是通過doSignal完畢。注意這里傳遞的是firstWaiter指向的節點,也就是喚醒的時候,是從隊列頭開始喚醒的。
從尾部進入,從頭部喚醒。所以這里的等待隊列是一個FIFO隊列。
private?void?doSignal?(Node first) {
????????????do?{
????????????????if?( (firstWaiter?= first.nextWaiter) ==?null)
????????????????????lastWaiter?=?null?;
??????????????? first.?nextWaiter?=?null?;
??????????? }?while?(!transferForSignal(first) &&
???????????????????? (first =?firstWaiter) !=?null?);
??????? }
doSignal方法把第一個節點從條件對象的等待隊列中移除,然后終于是走到transferForSignal中來進行操作。
final?boolean?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);
????????int?ws = p.waitStatus?;
????????if?(ws > 0 || !compareAndSetWaitStatus(p, ws, Node.?SIGNAL))
??????????? LockSupport.?unpark(node.thread);
????????return?true?;
??? }
通過enq方法,把線程所在的節點增加到鎖對象的等待隊列中,這樣在條件合適的時候,線程被喚醒,獲得鎖,然后運行。