Java併發編程J.U.C之Condition

在上一篇中,咱們瞭解了下J.U.C的鎖的獲取與釋放的過程,這個過程主要經過在A.Q.S中維持一個等待隊列來實現,其中咱們也提到了,在A.Q.S中除了一個等待隊列以外,還有一個Condition隊列,在瞭解Condition隊列以前,先來看一下Condition是怎麼回事: node

The synchronizer framework provides a ConditionObject class for use by synchronizers that maintain exclusive synchronization and conform to the Lock interface. Any number of condition objects may be attached to a lock object, providing classic monitor-style await, signal, and signalAll operations, including those with timeouts, along with some inspection and monitoring methods. 框架

上面的這一段內容摘自Doug Lea的AQS論文,從上面這一段話能夠看出,Condition主要是爲了在J.U.C框架中提供和Java傳統的監視器風格的wait,notify和notifyAll方法相似的功能,那麼先來解釋一下這三個方法的做用: less

  • Object.wait()方法:使當前線程釋放Object上的監視器而且掛起,直到有另外的線程調用Object.notify()方法或者Object.notifyAll()方法喚醒當前線程,當被喚醒後,Object.wait()方法會嘗試從新獲取監視器,成功獲取後繼續往下執行。注意Object.wait()方法只有在當前線程持有Object的監視器的時候纔可以調用,否則會拋出異常。
  • Object.notify()方法:用於喚醒另一個調用了Object.wait()方法的線程,若是有多個都調用了Object.wait()方法,那麼就會選擇一個線程去notify(),具體選擇哪個和具體的實現有關,當前線程在調用Object.notify()方法之後會就釋放Object的監視器,和wait()方法同樣,Object.notify()方法只有在當前線程只有Object的監視器的時候纔可以調用,否則就會拋出異常。
  • Object.notifyAll()方法:喚醒全部調用了Object.wait()方法的線程,若是有多個線程調用了Object.wait()方法,那麼就會引起這些線程之間的競爭,最後誰成功獲取到Object的監視器和具體的實現有關,當前線程在調用Object.notifyAll()方法之後會就釋放Object的監視器,和wait()方法同樣,Object.notifyAll()方法只有在當前線程只有Object的監視器的時候纔可以調用,否則就會拋出異常。

那麼Condition是如何實現wait,notify和notifyAll方法的功能呢?咱們接下來看: ide

在Condition中,wait,notify和notifyAll方法分別對應了await,signal和signalAll方法,固然Condition也提供了超時的、不可被中斷的await()方法,不過咱們主要仍是看一看await,notify和notifyAll的實現,先看await: 動畫

await方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
publicfinalvoidawait()throwsInterruptedException {
    if(Thread.interrupted())
        thrownewInterruptedException();
    Node node = addConditionWaiter();
    intsavedState = fullyRelease(node);
    intinterruptMode =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);
}

整個await的過程以下: ui

  1. 在第2行處,若是當前線程被中斷,則拋出中斷異常。
  2. 在第4行處,將節點加入到Condition隊列中去,這裏若是lastWaiter是cancel狀態,那麼會把它踢出Condition隊列。
  3. 在第5行處,調用tryRelease,釋放當前線程的鎖
  4. 在第7行處,判斷節點是否在等待隊列中(signal操做會將Node從Condition隊列中拿出而且放入到等待隊列中去),若是不在等待隊列中了,就park當前線程,若是在,就退出循環,這個時候若是被中斷,那麼就退出循環
  5. 在第12行處,這個時候線程已經被signal()或者signalAll()操做給喚醒了,退出了4中的while循環,嘗試再次獲取鎖,調用acquireQueued方法。

能夠看到,這個await的操做過程和Object.wait()方法是同樣,只不過await()採用了Condition隊列的方式實現了Object.wait()的功能。 this

signal和signalAll方法:

在瞭解了await方法的實現之後,signal和signalAll方法的實現就相對簡單了,先看看signal方法: spa

1
2
3
4
5
6
7
publicfinalvoidsignal() {
    if(!isHeldExclusively())
        thrownewIllegalMonitorStateException();
    Node first = firstWaiter;
    if(first !=null)
        doSignal(first);
}

這裏先判斷當前線程是否持有鎖,若是沒有持有,則拋出異常,而後判斷整個condition隊列是否爲空,不爲空則調用doSignal方法來喚醒線程,看看doSignal方法都幹了一些什麼: 線程

1
2
3
4
5
6
7
8
privatevoiddoSignal(Node first) {
    do{
        if( (firstWaiter = first.nextWaiter) ==null)
            lastWaiter =null;
        first.nextWaiter =null;
    }while(!transferForSignal(first) &&
             (first = firstWaiter) !=null);
}

這個while循環的做用就是將firstWaiter往Condition隊列的後面移一位,而且喚醒first,看看while循環中tranferForSignal: orm

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
finalbooleantransferForSignal(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);
    intc = p.waitStatus;
    if(c >0|| !compareAndSetWaitStatus(p, c, Node.SIGNAL))
        LockSupport.unpark(node.thread);
    returntrue;
}

這段代碼的做用就是修改Node的waitStatus爲0,而後將Node插入到等待隊列中,而且喚醒Node。

signalAll和signal方法相似,主要的不一樣在於它不是調用doSignal方法,而是調用doSignalAll方法:

1
2
3
4
5
6
7
8
9
privatevoiddoSignalAll(Node first) {
    lastWaiter = firstWaiter  =null;
    do{
        Node next = first.nextWaiter;
        first.nextWaiter =null;
        transferForSignal(first);
        first = next;
    }while(first !=null);
}

這個方法就至關於把Condition隊列中的全部Node所有取出插入到等待隊列中去。

總結:

在瞭解了await(),signal()和signalAll方法的實現之後,咱們再來經過一副gif動畫來看一看這一個總體的過程:
animation

相關文章
相關標籤/搜索