歡迎來到《併發王者課》,本文是該系列文章中的第19篇。
在上一篇文章中,我們介紹了阻塞佇列。如果你閱讀過它的原始碼,那麼你一定會注意到原始碼有兩個Condition型別的變數:notEmpty
和notFull
,在讀寫佇列時你也會注意到它們是如何被使用的。事實上,在使用JUC中的各種鎖時,Condition都很有用場,你很有必要了解它。所以,本文就為你介紹它的來龍去脈和用法。
在前面的系列文章中,我們多次提到過synchronized
關鍵字,相信你已經對它的用法瞭如於心。在多執行緒協作時,有兩個另外的關鍵字經常和synchronized
一同出現,它們相互配合,就是wait
和notify
,比如下面的這段程式碼:
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的原生同步工具,wait
和notify
是它的原生搭檔。然而,在鉑金系列中,我們已經開始了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是wait
、notify
和notifyAll
增強版本,wait
和notify
有的能力它都有,wait
和notify
沒有的能力它也有。
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的配套,在理解時要結合原生的wait
和notify
去理解。關於Condition與它們之間的詳細區別,已經都在下面的表格裡:
對比項 | Object's Monitor methods | Condition |
---|---|---|
前置條件 | 獲取物件的鎖 | 呼叫Lock獲取鎖,呼叫lock.newCondition()獲取Condition物件 |
呼叫方式 | 直接呼叫,如object.wait() | 直接呼叫,如condition.await() |
等待佇列個數 | 一個 | 多個 |
當前執行緒釋放鎖並進入等待狀態 | ✔︎ | ✔︎ |
當前執行緒釋放鎖並進入等待狀態,在等待時不響應中斷 | ✘ | ✔︎ |
當前執行緒釋放鎖並進入超時等待 | ✔︎ | ✔︎ |
當前執行緒釋放鎖並進入等待到未來某個時刻 | ✘ | ✔︎ |
喚醒等待佇列中的某一個執行緒 | ✔︎ | ✔︎ |
喚醒等待佇列中的全部執行緒 | ✔︎ | ✔︎ |
理解表格中的各項差異,不要死記硬背,而是要基於Condition介面中定義的方法,從關鍵處理解它們的不同。
正文到此結束,恭喜你又上了一顆星✨
夫子的試煉
- 編寫程式碼使用Condition喚醒指定執行緒。
延伸閱讀與參考資料
-
小結表格中的內容由網路圖片提取,未能找到原始出處,知道的請評論告知,感謝!
最新修訂及更好閱讀體驗
關於作者
關注公眾號【技術八點半】,及時獲取文章更新。傳遞有品質的技術文章,記錄平凡人的成長故事,偶爾也聊聊生活和理想。早晨8:30推送作者品質原創,晚上20:30推送行業深度好文。
如果本文對你有幫助,歡迎點贊、關注、監督,我們一起從青銅到王者。