1. 程式人生 > >Java併發包下的鎖(4)——Condition介面

Java併發包下的鎖(4)——Condition介面

Condition 介面提供了類似Object的監視器方法,與Lock配合可以實現 等待/通知 模式

Condition的介面

在說Condition的介面之前,先對比一下與Object監視器的異同:

對比項 Object的監視器(Monitor) Condition
前置條件 獲取物件的鎖 呼叫Lock.lock()獲取鎖呼叫Lock.newCondition獲取Condition物件
呼叫方式 直接呼叫:object.wait() 直接呼叫:condition.await()
等待佇列個數 一個 多個
當前執行緒釋放鎖並進入等待狀態 支援 支援
當前執行緒釋放鎖並進入等待狀態,在等待狀態中不響應中斷 不支援 支援
當前執行緒釋放鎖並進入超時等待狀態 支援 支援
當前執行緒釋放鎖並進入超時等待狀態到將來某一個時間 不支援 支援
喚醒等待佇列中的一個執行緒 支援 支援
喚醒等待佇列中的全部執行緒 支援 支援

 Condition介面的實現在兩個佇列同步器:AbstractQueuedSynchronizer和AbstractQueuedLongSynchronizer中,它們通過一個內部類ConditionObject聚合了其中的操作。下面來看看Condition提供的API有哪些:

// 呼叫該方法執行緒會釋放鎖並進入等待狀態。呼叫該方法的執行緒要想進入執行狀態的情況有:
// 其他執行緒呼叫該Condition的signal()或signalAll()方法,當前執行緒被選中喚醒
// 1. 其他執行緒中斷當前執行緒 // 2. 如果當前等待執行緒從await()方法返回,表名該執行緒已經獲取了Condition物件對應的鎖 void await() throws InterruptedException; // 對中斷不敏感 void awaitUninterruptibly(); // 當前執行緒進入等待狀態,直到被通知、中斷或者超時才返回。返回值表示剩餘的時間 long awaitNanos(long nanosTimeout) throws InterruptedException; // 當前執行緒進入等待狀態,直到被通知、中斷或者到某一個時間。到了指定時間返回true,否則返回false
boolean awaitUntil(Date deadline) throws InterruptedException; // 喚醒一個等待在Condition上的執行緒,該執行緒從等待方法返回前必須獲得與Condition相關聯的鎖 void signal(); // 喚醒所有等待在Condition上的執行緒,能夠從等待方法返回的執行緒必須與Condition相關聯的鎖 void signalAll();

下面的示例展示了Condition的基本用法:

Lock lock = new ReentrantLock();
Condition condition = lock.newCondition();

public void conditionWait() throws InterruptedException {
    lock.lock();
    try {
        condition.await()
    } finally {
        lock.unlock();
    }
}

public void conditionSignal() throws InterruptedException {
    lock.lock();
    try {
        condition.signal()
    } finally {
        lock.unlock();
    }
}
Condition的實現

 ConditionObject 是同步器 AQS 的內部類,每個 Condition 物件都包含著一個佇列,稱為等待佇列,該佇列是 Condition 物件實現等待/通知功能的關鍵。

1. 等待佇列

 等待佇列是一個FIFO的佇列,在佇列中每個節點都包含了一個執行緒引用,該執行緒就是在 Condition 物件上等待的執行緒,如果一個執行緒呼叫了 Condition.await() 方法,那麼該執行緒將會釋放鎖、構造成節點並加入等待佇列進入等待狀態。一個Condition包含一個等待佇列,Condition擁有首節點(firstWaiter)和尾節點(lastWaiter),當前執行緒呼叫 Condition.await() 方法,將會以當前執行緒構造節點,並將節點從尾部加入等待佇列。

image

 在節點的更新過程中,並不需要CAS的保證,因為呼叫await()方法的執行緒必定是已經獲取了鎖的執行緒,不會出現執行緒安全問題,該過程是由鎖的機制來保證的。

 在Object的監視器模型上,一個物件擁有一個同步佇列和等待佇列,而併發包中的Lock(同步器)擁有一個同步佇列和多個等待佇列,如下圖:

image

2. 等待

 當呼叫 await() 方法時,相當於同步佇列的首節點釋放了同步狀態,從同步佇列中移出,並構造成新的節點移動到Condition的等待佇列中

下面是 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) // clean up if cancelled
            unlinkCancelledWaiters();
        if (interruptMode != 0)
            reportInterruptAfterWait(interruptMode);
}

下圖是當前執行緒釋放同步狀態加入等待佇列的過程:

image

綜合上面所講,總結一下await()幹了些什麼事兒:

  • 當前執行緒為獲取了鎖的執行緒(同步佇列中的首節點)

  • 將當前執行緒構造成新的節點(等待節點),並加入等待佇列

  • 釋放該執行緒的同步狀態,喚醒同步對列中的後繼節點,然後該執行緒在等待佇列中等待

3. 通知

 呼叫Condition的signal()方法,將喚醒在等待佇列中等待時間最長的節點(首節點),在喚醒節點之前,會將節點移到同步佇列中。signal() 的原始碼如下:

public final void signal() {
    // 如果當前執行緒沒有獲取到鎖則丟擲異常
    if (!isHeldExclusively())
        throw new IllegalMonitorStateException();
    // 獲取等待佇列中的首節點
    Node first = firstWaiter;
    // 如果首節點不為空,將其移到同步佇列,並呼叫LockSupport喚醒節點中的執行緒
    if (first != null)
        doSignal(first);
}

節點從等待佇列移動到同步佇列的過程如下:

image

 成功獲取同步狀態後,被喚醒的執行緒將從先前呼叫的await()方法返回,此時該執行緒已經成功獲取了鎖。Condition的signalAll()方法,相當於對等待佇列中的每一個節點均執行一次signal()方法,其結果就是將等待佇列中的所有節點全部移入到同步佇列中,並喚醒每個節點的執行緒。

參考

《Java併發程式設計的藝術》