並發王者課-鉑金6:青出於藍-Condition如何把等待與通知玩出新花樣


歡迎來到《並發王者課》,本文是該系列文章中的第19篇

在上一篇文章中,我們介紹了阻塞隊列。如果你閱讀過它的源碼,那么你一定會注意到源碼有兩個Condition類型的變量:notEmptynotFull,在讀寫隊列時你也會注意到它們是如何被使用的。事實上,在使用JUC中的各種鎖時,Condition都很有用場,你很有必要了解它。所以,本文就為你介紹它的來龍去脈和用法。

在前面的系列文章中,我們多次提到過synchronized關鍵字,相信你已經對它的用法了如於心。在多線程協作時,有兩個另外的關鍵字經常和synchronized一同出現,它們相互配合,就是waitnotify,比如下面的這段代碼:

public class CountingSemaphore {
private int signals = 0;
public synchronized void take() {
this.signals++;
this.notify(); // 發送通知
}
public synchronized void release() throws InterruptedException {
while (this.signals == 0)
this.wait(); // 釋放鎖,進入等待
This.signals--;
}
}

synchronized是Java的原生同步工具,waitnotify是它的原生搭檔。然而,在鉑金系列中,我們已經開始了Lock接口和它的一些實現,比如可重入鎖ReentrantLock等。相比於synchronized,JUC所封裝的這些鎖工具在功能上要豐富得多,也更加容易使用。所以,相應地配套自然也要跟上,於是Condition就應運而生。

比如在上文的阻塞隊列中,Condition就已經閃亮登場:

public class LinkedBlockingQueue < E > extends AbstractQueue < E >
implements BlockingQueue < E > , java.io.Serializable {

...省略源碼若干

// 定義Condition
// 注意,這里定義兩個Condition對象,用於喚醒不同的線程
private final Condition notEmpty = takeLock.newCondition();
private final Condition notFull = putLock.newCondition();

public E take() throws InterruptedException {
E x;
int c = -1;
final AtomicInteger count = this.count;
final ReentrantLock takeLock = this.takeLock;
takeLock.lockInterruptibly();
try {
while (count.get() == 0) {
// 進入等待
notEmpty.await();
}
x = dequeue();
c = count.getAndDecrement();
if (c > 1)
notEmpty.signal();
} finally {
takeLock.unlock();
}
if (c == capacity)
signalNotFull();
return x;
}

private void signalNotEmpty() {
final ReentrantLock takeLock = this.takeLock;
takeLock.lock();
try {
// 發送喚醒信號
notEmpty.signal();
} finally {
takeLock.unlock();
}
}
...省略源碼若干

}

從功能定位上說,作為Lock的配套工具,Condition是waitnotifynotifyAll增強版本,waitnotify有的能力它都有,waitnotify沒有的能力它也有

JUC中的Condition是以接口的形式出現,並定義了一些核心方法:

  • await():讓當前線程進入等待,直到收到信號或者被中斷;

  • await(long time, TimeUnit unit):讓當前線程進入等待,直到收到信號或者被中斷,或者到達指定的等待超時時間;

  • awaitNanos(long nanosTimeout):讓當前線程進入等待,直到收到信號或者被中斷,或者到達指定的等待超時時間,只是在時間單位上和上一個方法有所區別;

  • awaitUninterruptibly()讓當前線程進入等待,直到收到信號。注意,這個方法對中斷是不敏感的

  • awaitUntil(Date deadline)讓當前線程進入等待,直到收到信號或者被中斷,或者到達截止時間

  • signal():隨機喚醒一個線程;

  • signalAll():喚醒所有等待的線程。

從Condition的核心方法中可以看到,相較於原生的通知與等待,它的能力明顯增強了很多,比如awaitUninterruptibly()awaitUntil()。另外,Condition竟然是可以喚醒指定線程的,這就很有意思

作為接口,我們並不需要手動實現Condition,JUC已經提供了相關的實現,你可以在ReentrantLock中直接使用它。相關的類、接口之間的關系如下所示:

小結

以上就是關於Condition的全部內容。Condition並不復雜,它是JUC中Lock的配套,在理解時要結合原生的waitnotify去理解。關於Condition與它們之間的詳細區別,已經都在下面的表格里:

對比項 Object's Monitor methods Condition
前置條件 獲取對象的鎖 調用Lock獲取鎖,調用lock.newCondition()獲取Condition對象
調用方式 直接調用,如object.wait() 直接調用,如condition.await()
等待隊列個數 一個 多個
當前線程釋放鎖並進入等待狀態 ✔︎ ✔︎
當前線程釋放鎖並進入等待狀態,在等待時不響應中斷 ✔︎
當前線程釋放鎖並進入超時等待 ✔︎ ✔︎
當前線程釋放鎖並進入等待到未來某個時刻 ✔︎
喚醒等待隊列中的某一個線程 ✔︎ ✔︎
喚醒等待隊列中的全部線程 ✔︎ ✔︎

理解表格中的各項差異,不要死記硬背,而是要基於Condition接口中定義的方法,從關鍵處理解它們的不同。

正文到此結束,恭喜你又上了一顆星✨

夫子的試煉

  • 編寫代碼使用Condition喚醒指定線程。

延伸閱讀與參考資料

最新修訂及更好閱讀體驗

關於作者

關注公眾號【技術八點半】,及時獲取文章更新。傳遞有品質的技術文章,記錄平凡人的成長故事,偶爾也聊聊生活和理想。早晨8:30推送作者品質原創,晚上20:30推送行業深度好文。

如果本文對你有幫助,歡迎點贊關注監督,我們一起從青銅到王者


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM