Java 的 Condition 接口與等待通知機(jī)制詳解
一、引言
在 Java 并發(fā)編程里,實(shí)現(xiàn)線程間的協(xié)作與同步是極為關(guān)鍵的任務(wù)。除了使用 Object
類的 wait()
、notify()
和 notifyAll()
方法實(shí)現(xiàn)簡單的等待 - 通知機(jī)制外,Java 還提供了 Condition
接口,它與 ReentrantLock
配合使用,能實(shí)現(xiàn)更靈活、更精細(xì)的線程間通信。本文將深入探究 Condition
接口及其背后的等待通知機(jī)制。
二、Condition 接口概述
2.1 基本概念
Condition
接口位于 java.util.concurrent.locks
包中,它提供了類似 Object
類的 wait()
、notify()
和 notifyAll()
方法的功能,但 Condition
更為強(qiáng)大和靈活。Condition
實(shí)例是通過 Lock
對象的 newCondition()
方法創(chuàng)建的,每個 Lock
對象可以創(chuàng)建多個 Condition
實(shí)例,這使得我們可以針對不同的條件進(jìn)行線程的等待和喚醒操作。
2.2 與 Object 類等待通知方法的區(qū)別
- 關(guān)聯(lián)對象不同:
Object
類的wait()
、notify()
和notifyAll()
方法必須在synchronized
塊或方法中使用,它們關(guān)聯(lián)的是對象的內(nèi)部鎖;而Condition
接口的方法(如await()
、signal()
和signalAll()
)必須與Lock
對象配合使用,關(guān)聯(lián)的是Lock
對象。 - 靈活性不同:
Condition
可以創(chuàng)建多個等待隊(duì)列,允許更細(xì)粒度的線程控制。例如,一個線程可以等待某個特定的條件,而另一個線程可以喚醒等待該條件的線程,而Object
類的等待通知方法只能操作一個隱含的等待隊(duì)列。
三、Condition 接口的常用方法
3.1 await () 方法
await()
方法會使當(dāng)前線程進(jìn)入等待狀態(tài),直到其他線程調(diào)用該 Condition
的 signal()
或 signalAll()
方法,或者當(dāng)前線程被中斷。調(diào)用 await()
方法時,當(dāng)前線程會釋放持有的 Lock
,在被喚醒后,會重新獲取該 Lock
。示例代碼如下:
import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; public class AwaitExample { private final Lock lock = new ReentrantLock(); private final Condition condition = lock.newCondition(); public void awaitMethod() { lock.lock(); try { System.out.println("Thread " + Thread.currentThread().getName() + " is waiting."); condition.await(); System.out.println("Thread " + Thread.currentThread().getName() + " is awakened."); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } finally { lock.unlock(); } } }
3.2 signal () 方法
signal()
方法會喚醒在該 Condition
上等待的單個線程。如果有多個線程在等待,只會喚醒其中一個線程,具體喚醒哪個線程是不確定的。示例代碼如下:
import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; public class SignalExample { private final Lock lock = new ReentrantLock(); private final Condition condition = lock.newCondition(); public void signalMethod() { lock.lock(); try { System.out.println("Thread " + Thread.currentThread().getName() + " is signaling."); condition.signal(); } finally { lock.unlock(); } } }
3.3 signalAll () 方法
signalAll()
方法會喚醒在該 Condition
上等待的所有線程。被喚醒的線程會競爭獲取 Lock
,獲取到 Lock
的線程將繼續(xù)執(zhí)行。示例代碼如下:
import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; public class SignalAllExample { private final Lock lock = new ReentrantLock(); private final Condition condition = lock.newCondition(); public void signalAllMethod() { lock.lock(); try { System.out.println("Thread " + Thread.currentThread().getName() + " is signaling all."); condition.signalAll(); } finally { lock.unlock(); } } }
四、Condition 接口的應(yīng)用場景
4.1 生產(chǎn)者 - 消費(fèi)者模式
使用 Condition
接口可以實(shí)現(xiàn)更復(fù)雜的生產(chǎn)者 - 消費(fèi)者模式。例如,當(dāng)緩沖區(qū)滿時,生產(chǎn)者線程等待;當(dāng)緩沖區(qū)為空時,消費(fèi)者線程等待。示例代碼如下:
import java.util.LinkedList; import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; class Buffer { private final LinkedList<Integer> buffer = new LinkedList<>(); private static final int MAX_SIZE = 5; private final Lock lock = new ReentrantLock(); private final Condition notFull = lock.newCondition(); private final Condition notEmpty = lock.newCondition(); public void produce(int item) { lock.lock(); try { while (buffer.size() == MAX_SIZE) { System.out.println("Buffer is full, producer is waiting."); notFull.await(); } buffer.add(item); System.out.println("Produced: " + item); notEmpty.signal(); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } finally { lock.unlock(); } } public int consume() { lock.lock(); try { while (buffer.size() == 0) { System.out.println("Buffer is empty, consumer is waiting."); notEmpty.await(); } int item = buffer.removeFirst(); System.out.println("Consumed: " + item); notFull.signal(); return item; } catch (InterruptedException e) { Thread.currentThread().interrupt(); return -1; } finally { lock.unlock(); } } } public class ProducerConsumerWithCondition { public static void main(String[] args) { Buffer buffer = new Buffer(); Thread producer = new Thread(() -> { for (int i = 0; i < 10; i++) { buffer.produce(i); } }); Thread consumer = new Thread(() -> { for (int i = 0; i < 10; i++) { buffer.consume(); } }); producer.start(); consumer.start(); } }
4.2 多線程任務(wù)協(xié)調(diào)
在一些多線程任務(wù)中,需要根據(jù)不同的條件來協(xié)調(diào)線程的執(zhí)行順序。例如,一個線程需要等待其他幾個線程完成特定任務(wù)后才能繼續(xù)執(zhí)行,這時可以使用 Condition
接口來實(shí)現(xiàn)。
五、Condition 接口的實(shí)現(xiàn)原理
Condition
接口的實(shí)現(xiàn)通常依賴于 AbstractQueuedSynchronizer
(AQS)。每個 Condition
實(shí)例都有一個與之關(guān)聯(lián)的等待隊(duì)列,當(dāng)線程調(diào)用 await()
方法時,會將該線程封裝成一個節(jié)點(diǎn)加入到等待隊(duì)列中,并釋放持有的 Lock
。當(dāng)其他線程調(diào)用 signal()
或 signalAll()
方法時,會從等待隊(duì)列中移除相應(yīng)的節(jié)點(diǎn),并將其加入到 Lock
的同步隊(duì)列中,等待獲取 Lock
。
六、使用 Condition 接口的注意事項(xiàng)
6.1 鎖的獲取和釋放
在調(diào)用 Condition
接口的方法之前,必須先獲取關(guān)聯(lián)的 Lock
,并且在使用完后要確保釋放 Lock
,通常使用 try - finally
塊來保證這一點(diǎn)。
6.2 中斷處理
await()
方法會拋出 InterruptedException
異常,因此需要在代碼中進(jìn)行適當(dāng)?shù)漠惓L幚恚源_保線程在被中斷時能夠正確響應(yīng)。
6.3 條件判斷
在調(diào)用 await()
方法時,通常需要使用 while
循環(huán)來進(jìn)行條件判斷,而不是 if
語句。這是因?yàn)樵诙嗑€程環(huán)境下,線程被喚醒后可能會出現(xiàn)虛假喚醒的情況,使用 while
循環(huán)可以確保條件仍然滿足。
七、總結(jié)
Condition
接口為 Java 并發(fā)編程提供了一種強(qiáng)大而靈活的線程間等待通知機(jī)制。通過與 ReentrantLock
配合使用,可以實(shí)現(xiàn)更復(fù)雜、更精細(xì)的線程同步和協(xié)作。在實(shí)際開發(fā)中,根據(jù)具體的業(yè)務(wù)需求合理使用 Condition
接口,能夠提高程序的并發(fā)性能和可靠性。同時,需要注意鎖的獲取和釋放、中斷處理以及條件判斷等問題,以避免出現(xiàn)并發(fā)問題。
到此這篇關(guān)于探究 Java 的 Condition 接口與等待通知機(jī)制的文章就介紹到這了,更多相關(guān)java condition 等待通知機(jī)制內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
SpringBoot--- SpringSecurity進(jìn)行注銷權(quán)限控制的配置方法
這篇文章主要介紹了SpringBoot--- SpringSecurity進(jìn)行注銷,權(quán)限控制,本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2020-08-08SpringBoot+VUE實(shí)現(xiàn)數(shù)據(jù)表格的實(shí)戰(zhàn)
本文將使用VUE+SpringBoot+MybatisPlus,以前后端分離的形式來實(shí)現(xiàn)數(shù)據(jù)表格在前端的渲染,具有一定的參考價值,感興趣的可以了解一下2021-08-08SpringBoot使用redis實(shí)現(xiàn)session共享功能
這篇文章主要介紹了pringboot項(xiàng)目使用redis實(shí)現(xiàn)session共享,文中通過代碼示例講解的非常詳細(xì),對大家的學(xué)習(xí)或工作有一定的幫助,需要的朋友可以參考下2024-05-05logback的UNDEFINED_PROPERTY屬性源碼執(zhí)行流程解讀
這篇文章主要為大家介紹了logback的UNDEFINED_PROPERTY屬性源碼執(zhí)行流程解讀,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-11-11MybatisPlus如何自定義TypeHandler映射JSON類型為List
這篇文章主要介紹了MybatisPlus如何自定義TypeHandler映射JSON類型為List,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-01-01Java中ArrayList與順序表的概念與使用實(shí)例
順序表是指用一組地址連續(xù)的存儲單元依次存儲各個元素,使得在邏輯結(jié)構(gòu)上相鄰的數(shù)據(jù)元素存儲在相鄰的物理存儲單元中的線性表,下面這篇文章主要介紹了Java?ArrayList與順序表的相關(guān)資料,需要的朋友可以參考下2022-01-01