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)建多個等待隊列,允許更細(xì)粒度的線程控制。例如,一個線程可以等待某個特定的條件,而另一個線程可以喚醒等待該條件的線程,而Object類的等待通知方法只能操作一個隱含的等待隊列。
三、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)的等待隊列,當(dāng)線程調(diào)用 await() 方法時,會將該線程封裝成一個節(jié)點(diǎn)加入到等待隊列中,并釋放持有的 Lock。當(dāng)其他線程調(diào)用 signal() 或 signalAll() 方法時,會從等待隊列中移除相應(yīng)的節(jié)點(diǎn),并將其加入到 Lock 的同步隊列中,等待獲取 Lock。
六、使用 Condition 接口的注意事項
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-08
SpringBoot+VUE實(shí)現(xiàn)數(shù)據(jù)表格的實(shí)戰(zhàn)
本文將使用VUE+SpringBoot+MybatisPlus,以前后端分離的形式來實(shí)現(xiàn)數(shù)據(jù)表格在前端的渲染,具有一定的參考價值,感興趣的可以了解一下2021-08-08
SpringBoot使用redis實(shí)現(xiàn)session共享功能
這篇文章主要介紹了pringboot項目使用redis實(shí)現(xiàn)session共享,文中通過代碼示例講解的非常詳細(xì),對大家的學(xué)習(xí)或工作有一定的幫助,需要的朋友可以參考下2024-05-05
logback的UNDEFINED_PROPERTY屬性源碼執(zhí)行流程解讀
這篇文章主要為大家介紹了logback的UNDEFINED_PROPERTY屬性源碼執(zhí)行流程解讀,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-11-11
MybatisPlus如何自定義TypeHandler映射JSON類型為List
這篇文章主要介紹了MybatisPlus如何自定義TypeHandler映射JSON類型為List,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-01-01
Java中ArrayList與順序表的概念與使用實(shí)例
順序表是指用一組地址連續(xù)的存儲單元依次存儲各個元素,使得在邏輯結(jié)構(gòu)上相鄰的數(shù)據(jù)元素存儲在相鄰的物理存儲單元中的線性表,下面這篇文章主要介紹了Java?ArrayList與順序表的相關(guān)資料,需要的朋友可以參考下2022-01-01

