Java生產(chǎn)者和消費(fèi)者實(shí)現(xiàn)等待喚醒機(jī)制
本文將介紹如何使用Java多線程實(shí)現(xiàn)經(jīng)典的生產(chǎn)者-消費(fèi)者模型,通過(guò)synchronized、wait()和notifyAll()實(shí)現(xiàn)線程間的安全協(xié)作。
模型概述
生產(chǎn)者-消費(fèi)者模型是多線程編程中的經(jīng)典案例,主要解決以下問(wèn)題:
- 生產(chǎn)者線程負(fù)責(zé)生產(chǎn)數(shù)據(jù)/資源
- 消費(fèi)者線程負(fù)責(zé)消費(fèi)數(shù)據(jù)/資源
- 生產(chǎn)者和消費(fèi)者通過(guò)共享緩沖區(qū)進(jìn)行通信
- 需要解決線程同步和資源競(jìng)爭(zhēng)問(wèn)題
完整代碼實(shí)現(xiàn)
1. Desk.java(共享數(shù)據(jù)類)
/** * 共享數(shù)據(jù)類 * - foodflag: 食物狀態(tài)標(biāo)記(0=無(wú)食物,1=有食物) * - count: 剩余食物數(shù)量 * - lock: 同步鎖對(duì)象 */ public class Desk { public static int foodflag = 0; public static int count = 10; public static final Object lock = new Object(); }
2. Cook.java(生產(chǎn)者線程)
/** * 生產(chǎn)者線程 - 廚師 */ public class Cook extends Thread { @Override public void run() { while (true) { synchronized (Desk.lock) { // 如果食物已全部生產(chǎn)完成,則退出 if (Desk.count == 0) { break; } // 如果還有食物未被消費(fèi),則等待 if (Desk.foodflag == 1) { try { Desk.lock.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } else { // 生產(chǎn)食物 System.out.println(Thread.currentThread().getName() + " 做了一碗面"); Desk.foodflag = 1; // 通知消費(fèi)者可以消費(fèi)了 Desk.lock.notifyAll(); } } } System.out.println(Thread.currentThread().getName() + " 停止生產(chǎn)"); } }
3. Foodie.java(消費(fèi)者線程)
/** * 消費(fèi)者線程 - 吃貨 */ public class Foodie extends Thread { @Override public void run() { while (true) { synchronized (Desk.lock) { // 如果食物已全部消費(fèi)完,則退出 if (Desk.count == 0) { Desk.lock.notifyAll(); // 確保生產(chǎn)者線程能退出 break; } // 如果沒(méi)有食物,則等待 if (Desk.foodflag == 0) { try { Desk.lock.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } else { // 消費(fèi)食物 Desk.count--; System.out.println(Thread.currentThread().getName() + " 吃了一碗,還剩 " + Desk.count + " 碗"); Desk.foodflag = 0; // 通知生產(chǎn)者可以生產(chǎn)了 Desk.lock.notifyAll(); } } } System.out.println(Thread.currentThread().getName() + " 停止消費(fèi)"); } }
4. Main.java(測(cè)試類)
/** * 測(cè)試類 */ public class Main { public static void main(String[] args) { // 創(chuàng)建并啟動(dòng)生產(chǎn)者線程 Cook cook = new Cook(); cook.setName("廚師"); // 創(chuàng)建并啟動(dòng)消費(fèi)者線程 Foodie foodie = new Foodie(); foodie.setName("吃貨"); cook.start(); foodie.start(); } }
關(guān)鍵點(diǎn)解析
1. 同步機(jī)制
我們使用synchronized
關(guān)鍵字實(shí)現(xiàn)對(duì)共享資源的互斥訪問(wèn):
synchronized (Desk.lock) { // 臨界區(qū)代碼 }
2. 線程通信
通過(guò)wait()
和notifyAll()
實(shí)現(xiàn)線程間協(xié)作:
wait()
:釋放鎖并進(jìn)入等待狀態(tài)notifyAll()
:?jiǎn)拘阉械却撴i的線程
3. 生產(chǎn)消費(fèi)邏輯
生產(chǎn)者檢查
foodflag
:為0時(shí)生產(chǎn)食物并設(shè)置
foodflag=1
為1時(shí)等待消費(fèi)者消費(fèi)
消費(fèi)者檢查
foodflag
:為1時(shí)消費(fèi)食物并設(shè)置
foodflag=0
為0時(shí)等待生產(chǎn)者生產(chǎn)
執(zhí)行結(jié)果示例
廚師 做了一碗面
吃貨 吃了一碗,還剩 9 碗
廚師 做了一碗面
吃貨 吃了一碗,還剩 8 碗
...
吃貨 吃了一碗,還剩 0 碗
廚師 停止生產(chǎn)
吃貨 停止消費(fèi)
常見(jiàn)問(wèn)題解答
Q1: 為什么使用notifyAll()而不是notify()?
notifyAll()
會(huì)喚醒所有等待線程,讓它們公平競(jìng)爭(zhēng)鎖,避免某些線程長(zhǎng)期得不到執(zhí)行的情況。而notify()
只隨機(jī)喚醒一個(gè)線程,可能導(dǎo)致線程饑餓。
Q2: 為什么要在finally塊外調(diào)用notifyAll()?
因?yàn)閚otifyAll()必須在持有鎖的情況下調(diào)用,而synchronized塊結(jié)束時(shí)鎖會(huì)自動(dòng)釋放,所以不需要在finally中調(diào)用。
Q3: 如何避免死鎖?
確保每個(gè)
wait()
都有對(duì)應(yīng)的notifyAll()
設(shè)置合理的退出條件(如
count == 0
)避免嵌套鎖
總結(jié)
通過(guò)這個(gè)案例,我們學(xué)習(xí)了:
如何使用
synchronized
實(shí)現(xiàn)線程同步如何使用
wait()
/notifyAll()
實(shí)現(xiàn)線程通信生產(chǎn)者-消費(fèi)者模型的經(jīng)典實(shí)現(xiàn)
多線程編程中的常見(jiàn)問(wèn)題及解決方案
這個(gè)模型可以應(yīng)用于許多實(shí)際場(chǎng)景,如消息隊(duì)列、任務(wù)調(diào)度等系統(tǒng)設(shè)計(jì)中。
到此這篇關(guān)于Java生產(chǎn)者和消費(fèi)者實(shí)現(xiàn)等待喚醒機(jī)制的文章就介紹到這了,更多相關(guān)Java 等待喚醒機(jī)制內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
SpringCloud?Gateway讀取Request?Body方式
這篇文章主要介紹了SpringCloud?Gateway讀取Request?Body方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-03-03jar包手動(dòng)添加到本地maven倉(cāng)庫(kù)的步驟詳解
在寫(xiě)程序的過(guò)程中,有時(shí)候會(huì)遇到私服里沒(méi)有需要的jar包的情況,這時(shí)候我們就可以手動(dòng)導(dǎo)入jar包到本地倉(cāng)庫(kù)進(jìn)行使用,下面這篇文章主要給大家介紹了關(guān)于jar包手動(dòng)添加到本地maven倉(cāng)庫(kù)的相關(guān)資料,需要的朋友可以參考下2022-08-08一文帶你了解Spring的Bean初始化過(guò)程和生命周期
Spring的核心功能有三點(diǎn)IOC、DI、AOP,IOC則是基礎(chǔ),也是Spring功能的最核心的點(diǎn)之一。今天一起來(lái)總結(jié)下Spring中Bean是怎么被創(chuàng)建出來(lái)的2023-03-03javax.validation自定義日期范圍校驗(yàn)注解操作
這篇文章主要介紹了javax.validation自定義日期范圍校驗(yàn)注解操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2020-09-09IntelliJ IDEA 2020.2正式發(fā)布,兩點(diǎn)多多總能助你提效
這篇文章主要介紹了IntelliJ IDEA 2020.2正式發(fā)布,諸多亮點(diǎn)總有幾款能助你提效,本文通過(guò)圖文實(shí)例代碼相結(jié)合給大家介紹的非常詳細(xì),需要的朋友可以參考下2020-07-07Mybatis使用@one和@Many實(shí)現(xiàn)一對(duì)一及一對(duì)多關(guān)聯(lián)查詢
本文主要介紹了Mybatis使用@one和@Many實(shí)現(xiàn)一對(duì)一及一對(duì)多關(guān)聯(lián)查詢,文中通過(guò)示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-09-09springboot內(nèi)嵌Tomcat安全漏洞修復(fù)方式
針對(duì)CVE-2020-1938漏洞,建議升級(jí)Tomcat至安全版本以避免受影響,影響版本包括:Apache Tomcat 9.x小于9.0.31、Apache Tomcat 8.x小于8.5.51、Apache Tomcat 7.x小于7.0.100及Apache Tomcat 6.x,2024-10-10