一文詳解Java?Condition的await和signal等待通知機(jī)制
我現(xiàn)在得仔細(xì)理解Java中Condition的await和signal機(jī)制。首先,我知道Java里處理并發(fā)問題通常會(huì)用到synchronized關(guān)鍵字和Object的wait()、notify()方法。但后來引入了Lock和Condition,提供了更靈活的線程同步方式。那Condition到底是怎么工作的呢?特別是await和signal這兩個(gè)方法。
Java中的Condition
接口提供了比傳統(tǒng)synchronized
配合wait()
和notify()
更靈活的線程等待/通知機(jī)制。結(jié)合Lock
使用,Condition
允許更細(xì)粒度的線程控制。以下是其核心機(jī)制與使用要點(diǎn)的總結(jié):
1. Condition的核心方法
await()
:釋放當(dāng)前鎖,使線程進(jìn)入等待狀態(tài),直到以下情況發(fā)生:- 被
signal()
或signalAll()
喚醒。 - 線程被中斷(拋出
InterruptedException
)。 - 支持超時(shí)或指定截止時(shí)間的變體方法(如
awaitNanos()
、awaitUntil()
)。
- 被
signal()
:?jiǎn)拘岩粋€(gè)等待在此Condition
上的線程(隨機(jī)選擇)。signalAll()
:?jiǎn)拘阉械却诖?code>Condition上的線程。
2. 使用場(chǎng)景與優(yōu)勢(shì)
- 多條件變量:一個(gè)
Lock
可關(guān)聯(lián)多個(gè)Condition
,每個(gè)條件代表不同的等待條件(如生產(chǎn)者-消費(fèi)者模型中的“隊(duì)列非空”和“隊(duì)列未滿”)。 - 精準(zhǔn)喚醒:避免無意義的線程競(jìng)爭(zhēng)(如生產(chǎn)者只喚醒消費(fèi)者,而非其他生產(chǎn)者)。
- 靈活性:支持中斷、超時(shí)和非阻塞嘗試等待。
3. 使用流程與規(guī)范
那么,在生產(chǎn)者-消費(fèi)者模型中如何應(yīng)用呢?假設(shè)我們有一個(gè)有限容量的隊(duì)列,當(dāng)隊(duì)列滿時(shí),生產(chǎn)者線程需要等待notFull條件;當(dāng)隊(duì)列空時(shí),消費(fèi)者線程需要等待notEmpty條件。生產(chǎn)者生產(chǎn)一個(gè)元素后,會(huì)signal notEmpty條件,喚醒消費(fèi)者;消費(fèi)者消費(fèi)一個(gè)元素后,會(huì)signal notFull條件,喚醒生產(chǎn)者。這樣的實(shí)現(xiàn)會(huì)比使用synchronized更高效,因?yàn)槊看螁拘训木€程都是確實(shí)需要被喚醒的,而不是所有線程都被喚醒。
基本模板
Lock lock = new ReentrantLock(); Condition condition = lock.newCondition(); lock.lock(); try { while (條件不滿足) { // 必須用while循環(huán)檢查條件,防止虛假喚醒 condition.await(); } // 執(zhí)行業(yè)務(wù)邏輯 } catch (InterruptedException e) { // 處理中斷 } finally { lock.unlock(); }
生產(chǎn)者-消費(fèi)者示例
class BoundedBuffer { final Lock lock = new ReentrantLock(); final Condition notFull = lock.newCondition(); // 隊(duì)列未滿條件 final Condition notEmpty = lock.newCondition(); // 隊(duì)列非空條件 final Object[] items = new Object[100]; int putPtr, takePtr, count; public void put(Object x) throws InterruptedException { lock.lock(); try { while (count == items.length) // 隊(duì)列滿,等待非滿條件 notFull.await(); items[putPtr] = x; if (++putPtr == items.length) putPtr = 0; count++; notEmpty.signal(); // 生產(chǎn)后喚醒消費(fèi)者 } finally { lock.unlock(); } } public Object take() throws InterruptedException { lock.lock(); try { while (count == 0) // 隊(duì)列空,等待非空條件 notEmpty.await(); Object x = items[takePtr]; if (++takePtr == items.length) takePtr = 0; count--; notFull.signal(); // 消費(fèi)后喚醒生產(chǎn)者 return x; } finally { lock.unlock(); } } }
這里,使用signal還是signalAll要看具體情況。如果每次生產(chǎn)或消費(fèi)一個(gè)元素,只需要喚醒一個(gè)對(duì)應(yīng)的消費(fèi)者或生產(chǎn)者,那么signal足夠,效率更高。但如果存在多個(gè)消費(fèi)者或生產(chǎn)者可能同時(shí)滿足條件,可能需要用signalAll來避免死鎖或者保證所有可能被喚醒的線程都能繼續(xù)執(zhí)行。
不過,使用signalAll可能會(huì)導(dǎo)致“驚群效應(yīng)”,即多個(gè)線程被喚醒但只有一個(gè)能繼續(xù)執(zhí)行,其他線程又進(jìn)入等待。這時(shí)候可能影響性能。所以要根據(jù)具體情況選擇。
另外,需要注意,調(diào)用signal或signalAll的時(shí)候,必須持有對(duì)應(yīng)的鎖,否則會(huì)拋出IllegalMonitorStateException。這是因?yàn)檫@些方法需要修改條件變量的狀態(tài),而這些狀態(tài)通常由鎖保護(hù),所以必須在鎖的范圍內(nèi)調(diào)用。
4. 關(guān)鍵注意事項(xiàng)
- 鎖的持有:調(diào)用
await()
、signal()
或signalAll()
前必須持有對(duì)應(yīng)的鎖,否則拋出IllegalMonitorStateException
。 - 循環(huán)檢查條件:使用
while
而非if
檢查條件,防止虛假喚醒(Spurious Wakeup)。 - 中斷處理:
await()
可能被中斷,需捕獲InterruptedException
并妥善處理。 - signal選擇:優(yōu)先使用
signal()
減少競(jìng)爭(zhēng),但需確保邏輯正確性;在復(fù)雜場(chǎng)景或不確定時(shí)使用signalAll()
。
然后是signal和signalAll方法。signal()會(huì)喚醒一個(gè)等待在Condition上的線程,而signalAll()會(huì)喚醒所有等待的線程。這和notify()、notifyAll()類似。不過因?yàn)镃ondition可以有多個(gè),所以喚醒的線程可能是針對(duì)某個(gè)特定條件的。比如,當(dāng)生產(chǎn)者生產(chǎn)了一個(gè)物品后,調(diào)用隊(duì)列不空條件的signal,這樣只有消費(fèi)者線程會(huì)被喚醒,而生產(chǎn)者線程可能在其他條件上等待,不會(huì)被影響。這樣就提高了效率,減少無意義的線程競(jìng)爭(zhēng)。
5. 與傳統(tǒng)機(jī)制的對(duì)比
我記得Lock替代了synchronized,而Condition替代了Object的監(jiān)視器方法。每個(gè)Lock可以有多個(gè)Condition,這樣不同的條件變量可以更精確地控制線程的等待和喚醒。比如說,一個(gè)生產(chǎn)者-消費(fèi)者模型里,可能有兩個(gè)條件,一個(gè)代表隊(duì)列不滿,另一個(gè)代表隊(duì)列不空。這樣生產(chǎn)者和消費(fèi)者可以分別在不同的條件上等待,避免像用Object的wait/notify時(shí)所有線程都在同一個(gè)條件上競(jìng)爭(zhēng),導(dǎo)致不必要的喚醒。
特性 | synchronized + wait()/notify() | Lock + Condition |
---|---|---|
鎖獲取方式 | 隱式獲取/釋放 | 顯式調(diào)用lock()和unlock() |
條件變量數(shù)量 | 每個(gè)對(duì)象僅一個(gè)等待隊(duì)列 | 一個(gè)Lock可關(guān)聯(lián)多個(gè)Condition |
超時(shí)/中斷支持 | 有限 | 支持超時(shí)、截止時(shí)間和不可中斷模式 |
靈活性 | 簡(jiǎn)單但不夠靈活 | 更細(xì)粒度的線程控制 |
那await方法的作用是什么呢?當(dāng)線程調(diào)用await()時(shí),應(yīng)該會(huì)釋放鎖,并進(jìn)入等待狀態(tài),直到被signal喚醒或者被中斷。這里可能需要和Object的wait()對(duì)比,await()同樣會(huì)釋放鎖,但需要結(jié)合Lock使用。調(diào)用await()前必須持有對(duì)應(yīng)的鎖,否則會(huì)拋出異常,比如IllegalMonitorStateException。那Condition的await()方法可能更靈活,因?yàn)樗梢员淮驍?,或者設(shè)置超時(shí)時(shí)間,比如awaitUninterruptibly()、awaitNanos()等方法,提供了更多控制選項(xiàng)。
6. 總結(jié)
Condition的await和signal機(jī)制提供了比傳統(tǒng)wait/notify更靈活和高效的線程通信方式。通過多個(gè)條件變量,可以精確控制線程的喚醒,減少不必要的競(jìng)爭(zhēng)和上下文切換,提升并發(fā)性能。使用時(shí)需要注意正確管理鎖,循環(huán)檢查條件,處理中斷,以及合理選擇signal或signalAll。
Condition
的await
和signal
機(jī)制通過多條件變量和顯式鎖控制,顯著提升了線程協(xié)作的效率和靈活性。適用于需要精確喚醒或復(fù)雜同步邏輯的場(chǎng)景(如線程池、阻塞隊(duì)列)。正確使用時(shí)需遵循鎖的規(guī)范,合理處理?xiàng)l件檢查與喚醒策略。
到此這篇關(guān)于一文詳解Java Condition的await和signal等待通知機(jī)制的文章就介紹到這了,更多相關(guān)Java Condition等待通知機(jī)制內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Spring Security使用權(quán)限注解實(shí)現(xiàn)精確控制
在現(xiàn)代的應(yīng)用系統(tǒng)中,權(quán)限管理是確保系統(tǒng)安全性的重要環(huán)節(jié),Spring Security作為Java世界最為普及的安全框架,提供了強(qiáng)大而靈活的權(quán)限控制功能,這篇文章將深入探討Spring Security使用權(quán)限注解實(shí)現(xiàn)精確控制,需要的朋友可以參考下2024-12-12Springboot與vue實(shí)現(xiàn)數(shù)據(jù)導(dǎo)出方法具體介紹
這篇文章主要介紹了Springboot與vue實(shí)現(xiàn)數(shù)據(jù)導(dǎo)出方法,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)吧2023-02-02Java代碼實(shí)現(xiàn)四種限流算法詳細(xì)介紹
本文主要介紹了Java代碼實(shí)現(xiàn)四種限流算法詳細(xì)介紹,包含固定窗口限流,滑動(dòng)窗口限流,漏桶限流,令牌桶限流,具有一定的參考價(jià)值,感興趣的可以了解一下2024-05-05Spring開發(fā)核心之AOP的實(shí)現(xiàn)與切入點(diǎn)持久化
面向?qū)ο缶幊淌且环N編程方式,此編程方式的落地需要使用“類”和 “對(duì)象”來實(shí)現(xiàn),所以,面向?qū)ο缶幊唐鋵?shí)就是對(duì) “類”和“對(duì)象” 的使用,面向切面編程,簡(jiǎn)單的說,就是動(dòng)態(tài)地將代碼切入到類的指定方法、指定位置上的編程思想就是面向切面的編程2022-10-10Spring Boot啟動(dòng)過程(五)之Springboot內(nèi)嵌Tomcat對(duì)象的start教程詳解
這篇文章主要介紹了Spring Boot啟動(dòng)過程(五)之Springboot內(nèi)嵌Tomcat對(duì)象的start的相關(guān)資料,需要的朋友可以參考下2017-04-04Idea為java程序添加啟動(dòng)參數(shù)(含:VM?options、Program?arguments、Environme
設(shè)置啟動(dòng)參數(shù)的意義就是當(dāng)啟動(dòng)程序時(shí),程序會(huì)優(yōu)先讀取idea的配置參數(shù),這樣就可以不用修改配置文件,下面這篇文章主要給大家介紹了關(guān)于Idea為java程序添加啟動(dòng)參數(shù)(含:VM?options、Program?arguments、Environment?variable)的相關(guān)資料,需要的朋友可以參考下2022-12-12SpringBoot之通過BeanPostProcessor動(dòng)態(tài)注入ID生成器案例詳解
這篇文章主要介紹了SpringBoot之通過BeanPostProcessor動(dòng)態(tài)注入ID生成器案例詳解,本篇文章通過簡(jiǎn)要的案例,講解了該項(xiàng)技術(shù)的了解與使用,以下就是詳細(xì)內(nèi)容,需要的朋友可以參考下2021-09-09Spring在多線程下@Resource注入為null的問題
這篇文章主要介紹了Spring在多線程下@Resource注入為null的問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-02-02