Java使用wait和notify實(shí)現(xiàn)線程之間的通信
??一. 為什么需要線程通信
線程是并發(fā)并行的執(zhí)行,表現(xiàn)出來是線程隨機(jī)執(zhí)行,但是我們?cè)趯?shí)際應(yīng)用中對(duì)線程的執(zhí)行順序是有要求的,這就需要用到線程通信
??線程通信為什么不使用優(yōu)先級(jí)來來解決線程的運(yùn)行順序?
總的優(yōu)先級(jí)是由線程pcb中的優(yōu)先級(jí)信息和線程等待時(shí)間共同決定的,所以一般開發(fā)中不會(huì)依賴優(yōu)先級(jí)來表示線程的執(zhí)行順序
??看下面這樣的一個(gè)場景:面包房的例子來描述生產(chǎn)者消費(fèi)者模型
有一個(gè)面包房,里面有面包師傅和顧客,對(duì)應(yīng)我們的生產(chǎn)者和消費(fèi)者,而面包房有一個(gè)庫存用來存儲(chǔ)面包,當(dāng)庫存滿了之后就不在生產(chǎn),同時(shí)消費(fèi)者也在購買面包,當(dāng)庫存面包賣完了之后,消費(fèi)者必須等待新的面包生產(chǎn)出來才能繼續(xù)購買
分析:對(duì)于何時(shí)停止生產(chǎn)何時(shí)停止消費(fèi)就需要應(yīng)用到線程通信來準(zhǔn)確的傳達(dá)生產(chǎn)和消費(fèi)信息
??二. wait和notify方法
??wait():讓當(dāng)前線程持有的對(duì)象鎖釋放并等待
??wait(long timeout):對(duì)應(yīng)的參數(shù)是線程等待的時(shí)間
??notify():喚醒使用同一個(gè)對(duì)象調(diào)用wait進(jìn)入等待的線程,重新競爭對(duì)象鎖
??notifyAll():如果有多個(gè)線程等待,notifyAll是全部喚醒 ,notify是隨機(jī)喚醒一個(gè)
?????注意:
??這幾個(gè)方法都屬于Object類中的方法
??必須使用在synchronized同步代碼塊/同步方法中
??哪個(gè)對(duì)象加鎖,就是用哪個(gè)對(duì)象wait,notify
??調(diào)用notify后不是立即喚醒,而是等synchronized結(jié)束以后,才喚醒
??1. wait()方法
??調(diào)用wait方法后:
??使執(zhí)行當(dāng)前代碼的線程進(jìn)行等待(線程放在等待隊(duì)列)
??釋放當(dāng)前的鎖
??滿足一定條件時(shí)被喚醒,重新嘗試獲取鎖
??wait等待結(jié)束的條件:
??其他線程調(diào)用該對(duì)象的notify方法
??wait等待時(shí)間超時(shí)(timeout參數(shù)來指定等待時(shí)間)
??其他線程調(diào)用interrupted方法,導(dǎo)致wait拋出InterruptedException異常
??2. notify()方法
當(dāng)使用wait不帶參數(shù)的方法時(shí),喚醒線程等待就需要使用notify方法
??這個(gè)方法是喚醒那些等待該對(duì)象的對(duì)象鎖的線程,使他們可以重新獲取該對(duì)象的對(duì)象鎖
??如果有多個(gè)線程等待,則由線程調(diào)度器隨機(jī)挑選出一個(gè)呈wait 狀態(tài)的線程(不存在先來后到)
??在notify()方法后,當(dāng)前線程不會(huì)馬上釋放該對(duì)象鎖,要等到執(zhí)行notify()方法的線程將程序執(zhí)行完,也就是退出同步代碼塊之后才會(huì)釋放對(duì)象鎖
??3. notifyAll()方法
該方法和notify()方法作用一樣,只是喚醒的時(shí)候,將所有等待的線程都喚醒
notify()方法只是隨機(jī)喚醒一個(gè)線程
??三. 使用wait和notify實(shí)現(xiàn)面包房業(yè)務(wù)
前提說明:
有2個(gè)面包師傅,面包師傅一次可以做出兩個(gè)面包
倉庫可以存儲(chǔ)100個(gè)面包
有10個(gè)消費(fèi)者,每個(gè)消費(fèi)者一次購買一個(gè)面包
?????注意:
消費(fèi)和生產(chǎn)是同時(shí)并發(fā)并行進(jìn)行的,不是一次生產(chǎn)一次消費(fèi)
?????實(shí)現(xiàn)代碼:
public class Bakery { private static int total;//庫存 public static void main(String[] args) { Producer producer = new Producer(); for(int i = 0;i < 2;i++){ new Thread(producer,"面包師傅-"+(i-1)).start(); } Consumer consumer = new Consumer(); for(int i = 0;i < 10;i++){ new Thread(consumer,"消費(fèi)者-"+(i-1)).start(); } } private static class Producer implements Runnable{ private int num = 3; //生產(chǎn)者每次生產(chǎn)三個(gè)面包 @Override public void run() { try { while(true){ //一直生產(chǎn) synchronized (Bakery.class){ while((total+num)>100){ //倉庫滿了,生產(chǎn)者等待 Bakery.class.wait(); } //等待解除 total += num; System.out.println(Thread.currentThread().getName()+"生產(chǎn)面包,庫存:"+total); Thread.sleep(500); Bakery.class.notifyAll(); //喚醒生產(chǎn) } Thread.sleep(500); } } catch (InterruptedException e) { e.printStackTrace(); } } } private static class Consumer implements Runnable{ private int num = 1; //消費(fèi)者每次消費(fèi)1個(gè)面包 @Override public void run() { try { while(true){ //一直消費(fèi) synchronized (Bakery.class){ while((total-num)<0){ //倉庫空了,消費(fèi)者等待 Bakery.class.wait(); } //解除消費(fèi)者等待 total -= num; System.out.println(Thread.currentThread().getName()+"消費(fèi)面包,庫存:"+total); Thread.sleep(500); Bakery.class.notifyAll(); //喚醒消費(fèi) } Thread.sleep(500); } } catch (InterruptedException e) { e.printStackTrace(); } } } }
部分打印結(jié)果:
??四. 阻塞隊(duì)列
阻塞隊(duì)列是一個(gè)特殊的隊(duì)列,也遵循“先進(jìn)先出”的原則,它是線程安全的隊(duì)列結(jié)構(gòu)
特性:典型的生產(chǎn)者消費(fèi)者模型,一般用于做任務(wù)的解耦和消峰
??隊(duì)列滿的時(shí)候,入隊(duì)列就堵塞等待(生產(chǎn)),直到有其他線程從隊(duì)列中取走元素
??隊(duì)列空的時(shí)候,出隊(duì)列就堵塞等待(消費(fèi)),直到有其他線程往隊(duì)列中插入元素
??1. 生產(chǎn)者消費(fèi)者模型
生產(chǎn)者消費(fèi)者模式就是通過一個(gè)容器來解決生產(chǎn)者和消費(fèi)者的強(qiáng)耦合問題
生產(chǎn)者和消費(fèi)者彼此之間不直接通信,而通過阻塞隊(duì)列來進(jìn)行通信,所以生產(chǎn)者生產(chǎn)完數(shù)據(jù)之后等待消費(fèi)者處理,直接扔給阻塞隊(duì)列,消費(fèi)者不找生產(chǎn)者要數(shù)據(jù),而是直接從阻塞隊(duì)列里取
阻塞隊(duì)列就相當(dāng)于一個(gè)緩沖區(qū),平衡了生產(chǎn)者和消費(fèi)者的處理能力
阻塞隊(duì)列也能使生產(chǎn)者和消費(fèi)者之間解耦
上述面包房業(yè)務(wù)的實(shí)現(xiàn)就是生產(chǎn)者消費(fèi)者模型的一個(gè)實(shí)例
??2. 標(biāo)準(zhǔn)庫中的阻塞隊(duì)列
在 Java 標(biāo)準(zhǔn)庫中內(nèi)置了阻塞隊(duì)列, 如果我們需要在一些程序中使用阻塞隊(duì)列, 直接使用標(biāo)準(zhǔn)庫中的即可
??BlockingQueue 是一個(gè)接口. 真正實(shí)現(xiàn)的類是 LinkedBlockingQueue
??put 方法用于阻塞式的入隊(duì)列, take 用于阻塞式的出隊(duì)列
??BlockingQueue 也有 offer, poll, peek 等方法, 但是這些方法不帶有阻塞特性
BlockingDeque<String> queue = new LinkedBlockingDeque<>(); queue.put("hello"); //如果隊(duì)列為空,直接出出隊(duì)列就會(huì)阻塞 String ret = queue.take(); System.out.println(ret);
??3. 阻塞隊(duì)列的模擬實(shí)現(xiàn)
這里使用數(shù)組實(shí)現(xiàn)一個(gè)循環(huán)隊(duì)列來模擬阻塞隊(duì)列
??當(dāng)隊(duì)列為空的時(shí)候,就不能取元素了,就進(jìn)入wait等待,當(dāng)有元素存放時(shí),喚醒
??當(dāng)隊(duì)列為滿的時(shí)候,就不能存元素了,就進(jìn)入wait等待,當(dāng)鈾元素取出時(shí),喚醒
?????實(shí)現(xiàn)代碼:
public class MyBlockingQueue { //使用數(shù)組實(shí)現(xiàn)一個(gè)循環(huán)隊(duì)列,隊(duì)列里面存放的是線程要執(zhí)行的任務(wù) private Runnable[] tasks; //隊(duì)列中任務(wù)的數(shù)量,根據(jù)數(shù)量來判斷是否可以存取 private int count; private int putIndex; //存放任務(wù)位置 private int takeIndex; //取出任務(wù)位置 //有參的構(gòu)造方法,表示隊(duì)列容量 public MyBlockingQueue(int size){ tasks = new Runnable[size]; } //存任務(wù) public void put(Runnable task){ try { synchronized (MyBlockingQueue.class){ //如果隊(duì)列容量滿了,則存任務(wù)等待 while(count == tasks.length){ MyBlockingQueue.class.wait(); } tasks[putIndex] = task; //將任務(wù)放入數(shù)組 putIndex = (putIndex+1) % tasks.length; //更新存任務(wù)位置 count++; //更新存放數(shù)量 MyBlockingQueue.class.notifyAll(); //喚醒存任務(wù) } } catch (InterruptedException e) { e.printStackTrace(); } } //取任務(wù) public Runnable take(){ try { synchronized (MyBlockingQueue.class){ //如果隊(duì)列任務(wù)為空,則取任務(wù)等待 while(count==0){ MyBlockingQueue.class.wait(); } //取任務(wù) Runnable task = tasks[takeIndex]; takeIndex = (takeIndex+1) % tasks.length; //更新取任務(wù)位置 count--; //更新存放數(shù)量 MyBlockingQueue.class.notifyAll(); //喚醒取任務(wù) return task; } } catch (InterruptedException e) { throw new RuntimeException("存放任務(wù)出錯(cuò)",e); } } }
??五. wait和sleep的區(qū)別(面試題)
相同點(diǎn):
都可以讓線程放棄執(zhí)行一段時(shí)間
不同點(diǎn):
??wait用于線程通信,讓線程在等待隊(duì)列中等待
??sleep讓線程阻塞一段時(shí)間,阻塞在阻塞隊(duì)列中
??wait需要搭配synchronized使用,sleep不用搭配
??wait是Object類的方法,sleep是Thread的靜態(tài)方法
總結(jié)
到此這篇關(guān)于Java使用wait和notify實(shí)現(xiàn)線程之間的通信的文章就介紹到這了,更多相關(guān)Java wait和notify實(shí)現(xiàn)線程通信內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
同時(shí)使用@LoadBalanced?@RefreshScope注解負(fù)載均衡失效分析
這篇文章主要為大家介紹了同時(shí)使用@LoadBalanced?@RefreshScope負(fù)載均衡失效問題分析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-02-02SpringCloud網(wǎng)關(guān)組件zuul實(shí)例解析
這篇文章主要介紹了SpringCloud網(wǎng)關(guān)組件zuul實(shí)例解析,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-03-03SpringCloud基于Feign的可編程式接口調(diào)用實(shí)現(xiàn)
本文主要介紹了SpringCloud基于Feign的可編程式接口調(diào)用實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2022-04-04Java實(shí)現(xiàn)TCP/IP協(xié)議的收發(fā)數(shù)據(jù)(服務(wù)端)代碼實(shí)例
這篇文章主要介紹了Java實(shí)現(xiàn)TCP/IP協(xié)議的收發(fā)數(shù)據(jù)(服務(wù)端)代碼實(shí)例,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2019-11-11