Java 的 Condition 接口與等待通知機(jī)制詳解
一、引言
在 Java 并發(fā)編程里,實(shí)現(xiàn)線程間的協(xié)作與同步是極為關(guān)鍵的任務(wù)。除了使用 Object
類的 wait()
、notify()
和 notifyAll()
方法實(shí)現(xiàn)簡(jiǎ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í)例是通過(guò) Lock
對(duì)象的 newCondition()
方法創(chuàng)建的,每個(gè) Lock
對(duì)象可以創(chuàng)建多個(gè) Condition
實(shí)例,這使得我們可以針對(duì)不同的條件進(jìn)行線程的等待和喚醒操作。
2.2 與 Object 類等待通知方法的區(qū)別
- 關(guān)聯(lián)對(duì)象不同:
Object
類的wait()
、notify()
和notifyAll()
方法必須在synchronized
塊或方法中使用,它們關(guān)聯(lián)的是對(duì)象的內(nèi)部鎖;而Condition
接口的方法(如await()
、signal()
和signalAll()
)必須與Lock
對(duì)象配合使用,關(guān)聯(lián)的是Lock
對(duì)象。 - 靈活性不同:
Condition
可以創(chuàng)建多個(gè)等待隊(duì)列,允許更細(xì)粒度的線程控制。例如,一個(gè)線程可以等待某個(gè)特定的條件,而另一個(gè)線程可以喚醒等待該條件的線程,而Object
類的等待通知方法只能操作一個(gè)隱含的等待隊(duì)列。
三、Condition 接口的常用方法
3.1 await () 方法
await()
方法會(huì)使當(dāng)前線程進(jìn)入等待狀態(tài),直到其他線程調(diào)用該 Condition
的 signal()
或 signalAll()
方法,或者當(dāng)前線程被中斷。調(diào)用 await()
方法時(shí),當(dāng)前線程會(huì)釋放持有的 Lock
,在被喚醒后,會(huì)重新獲取該 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()
方法會(huì)喚醒在該 Condition
上等待的單個(gè)線程。如果有多個(gè)線程在等待,只會(huì)喚醒其中一個(gè)線程,具體喚醒哪個(gè)線程是不確定的。示例代碼如下:
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()
方法會(huì)喚醒在該 Condition
上等待的所有線程。被喚醒的線程會(huì)競(jìng)爭(zhēng)獲取 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)用場(chǎng)景
4.1 生產(chǎn)者 - 消費(fèi)者模式
使用 Condition
接口可以實(shí)現(xiàn)更復(fù)雜的生產(chǎn)者 - 消費(fèi)者模式。例如,當(dāng)緩沖區(qū)滿時(shí),生產(chǎn)者線程等待;當(dāng)緩沖區(qū)為空時(shí),消費(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ù)不同的條件來(lái)協(xié)調(diào)線程的執(zhí)行順序。例如,一個(gè)線程需要等待其他幾個(gè)線程完成特定任務(wù)后才能繼續(xù)執(zhí)行,這時(shí)可以使用 Condition
接口來(lái)實(shí)現(xiàn)。
五、Condition 接口的實(shí)現(xiàn)原理
Condition
接口的實(shí)現(xiàn)通常依賴于 AbstractQueuedSynchronizer
(AQS)。每個(gè) Condition
實(shí)例都有一個(gè)與之關(guān)聯(lián)的等待隊(duì)列,當(dāng)線程調(diào)用 await()
方法時(shí),會(huì)將該線程封裝成一個(gè)節(jié)點(diǎn)加入到等待隊(duì)列中,并釋放持有的 Lock
。當(dāng)其他線程調(diào)用 signal()
或 signalAll()
方法時(shí),會(huì)從等待隊(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
塊來(lái)保證這一點(diǎn)。
6.2 中斷處理
await()
方法會(huì)拋出 InterruptedException
異常,因此需要在代碼中進(jìn)行適當(dāng)?shù)漠惓L幚?,以確保線程在被中斷時(shí)能夠正確響應(yīng)。
6.3 條件判斷
在調(diào)用 await()
方法時(shí),通常需要使用 while
循環(huán)來(lái)進(jìn)行條件判斷,而不是 if
語(yǔ)句。這是因?yàn)樵诙嗑€程環(huán)境下,線程被喚醒后可能會(huì)出現(xiàn)虛假喚醒的情況,使用 while
循環(huán)可以確保條件仍然滿足。
七、總結(jié)
Condition
接口為 Java 并發(fā)編程提供了一種強(qiáng)大而靈活的線程間等待通知機(jī)制。通過(guò)與 ReentrantLock
配合使用,可以實(shí)現(xiàn)更復(fù)雜、更精細(xì)的線程同步和協(xié)作。在實(shí)際開發(fā)中,根據(jù)具體的業(yè)務(wù)需求合理使用 Condition
接口,能夠提高程序的并發(fā)性能和可靠性。同時(shí),需要注意鎖的獲取和釋放、中斷處理以及條件判斷等問(wèn)題,以避免出現(xiàn)并發(fā)問(wèn)題。
到此這篇關(guān)于探究 Java 的 Condition 接口與等待通知機(jī)制的文章就介紹到這了,更多相關(guān)java condition 等待通知機(jī)制內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
SpringBoot--- SpringSecurity進(jìn)行注銷權(quán)限控制的配置方法
這篇文章主要介紹了SpringBoot--- SpringSecurity進(jìn)行注銷,權(quán)限控制,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-08-08SpringBoot+VUE實(shí)現(xiàn)數(shù)據(jù)表格的實(shí)戰(zhàn)
本文將使用VUE+SpringBoot+MybatisPlus,以前后端分離的形式來(lái)實(shí)現(xiàn)數(shù)據(jù)表格在前端的渲染,具有一定的參考價(jià)值,感興趣的可以了解一下2021-08-08SpringBoot使用redis實(shí)現(xiàn)session共享功能
這篇文章主要介紹了pringboot項(xiàng)目使用redis實(shí)現(xiàn)session共享,文中通過(guò)代碼示例講解的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作有一定的幫助,需要的朋友可以參考下2024-05-05logback的UNDEFINED_PROPERTY屬性源碼執(zhí)行流程解讀
這篇文章主要為大家介紹了logback的UNDEFINED_PROPERTY屬性源碼執(zhí)行流程解讀,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-11-11MybatisPlus如何自定義TypeHandler映射JSON類型為L(zhǎng)ist
這篇文章主要介紹了MybatisPlus如何自定義TypeHandler映射JSON類型為L(zhǎng)ist,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-01-01關(guān)于java的九個(gè)預(yù)定義Class對(duì)象
這篇文章主要介紹了關(guān)于java的九個(gè)預(yù)定義Class對(duì)象,在Java中,沒(méi)有類就無(wú)法做任何事情。然而,并不是所有的類都具有面向?qū)ο筇卣?。如Math.random,并只需要知道方法名和參數(shù),需要的朋友可以參考下2023-05-05Java中ArrayList與順序表的概念與使用實(shí)例
順序表是指用一組地址連續(xù)的存儲(chǔ)單元依次存儲(chǔ)各個(gè)元素,使得在邏輯結(jié)構(gòu)上相鄰的數(shù)據(jù)元素存儲(chǔ)在相鄰的物理存儲(chǔ)單元中的線性表,下面這篇文章主要介紹了Java?ArrayList與順序表的相關(guān)資料,需要的朋友可以參考下2022-01-01