您好,登錄后才能下訂單哦!
這篇“Java中怎么使用wait和notify實現線程間的通信”文章的知識點大部分人都不太理解,所以小編給大家總結了以下內容,內容詳細,步驟清晰,具有一定的借鑒價值,希望大家閱讀完這篇文章能有所收獲,下面我們一起來看看這篇“Java中怎么使用wait和notify實現線程間的通信”文章吧。
線程是并發并行的執行,表現出來是線程隨機執行,但是我們在實際應用中對線程的執行順序是有要求的,這就需要用到線程通信
線程通信為什么不使用優先級來來解決線程的運行順序?
總的優先級是由線程pcb中的優先級信息和線程等待時間共同決定的,所以一般開發中不會依賴優先級來表示線程的執行順序
看下面這樣的一個場景:面包房的例子來描述生產者消費者模型
有一個面包房,里面有面包師傅和顧客,對應我們的生產者和消費者,而面包房有一個庫存用來存儲面包,當庫存滿了之后就不在生產,同時消費者也在購買面包,當庫存面包賣完了之后,消費者必須等待新的面包生產出來才能繼續購買
分析:對于何時停止生產何時停止消費就需要應用到線程通信來準確的傳達生產和消費信息
wait():讓當前線程持有的對象鎖釋放并等待
wait(long timeout):對應的參數是線程等待的時間
notify():喚醒使用同一個對象調用wait進入等待的線程,重新競爭對象鎖
notifyAll():如果有多個線程等待,notifyAll是全部喚醒 ,notify是隨機喚醒一個
注意:
這幾個方法都屬于Object類中的方法
必須使用在synchronized同步代碼塊/同步方法中
哪個對象加鎖,就是用哪個對象wait,notify
調用notify后不是立即喚醒,而是等synchronized結束以后,才喚醒
調用wait方法后:
使執行當前代碼的線程進行等待(線程放在等待隊列)
釋放當前的鎖
滿足一定條件時被喚醒,重新嘗試獲取鎖
wait等待結束的條件:
其他線程調用該對象的notify方法
wait等待時間超時(timeout參數來指定等待時間)
其他線程調用interrupted方法,導致wait拋出InterruptedException異常
當使用wait不帶參數的方法時,喚醒線程等待就需要使用notify方法
這個方法是喚醒那些等待該對象的對象鎖的線程,使他們可以重新獲取該對象的對象鎖
如果有多個線程等待,則由線程調度器隨機挑選出一個呈wait 狀態的線程(不存在先來后到)
在notify()方法后,當前線程不會馬上釋放該對象鎖,要等到執行notify()方法的線程將程序執行完,也就是退出同步代碼塊之后才會釋放對象鎖
該方法和notify()方法作用一樣,只是喚醒的時候,將所有等待的線程都喚醒
notify()方法只是隨機喚醒一個線程
前提說明:
有2個面包師傅,面包師傅一次可以做出兩個面包
倉庫可以存儲100個面包
有10個消費者,每個消費者一次購買一個面包
注意:
消費和生產是同時并發并行進行的,不是一次生產一次消費
實現代碼:
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,"消費者-"+(i-1)).start(); } } private static class Producer implements Runnable{ private int num = 3; //生產者每次生產三個面包 @Override public void run() { try { while(true){ //一直生產 synchronized (Bakery.class){ while((total+num)>100){ //倉庫滿了,生產者等待 Bakery.class.wait(); } //等待解除 total += num; System.out.println(Thread.currentThread().getName()+"生產面包,庫存:"+total); Thread.sleep(500); Bakery.class.notifyAll(); //喚醒生產 } Thread.sleep(500); } } catch (InterruptedException e) { e.printStackTrace(); } } } private static class Consumer implements Runnable{ private int num = 1; //消費者每次消費1個面包 @Override public void run() { try { while(true){ //一直消費 synchronized (Bakery.class){ while((total-num)<0){ //倉庫空了,消費者等待 Bakery.class.wait(); } //解除消費者等待 total -= num; System.out.println(Thread.currentThread().getName()+"消費面包,庫存:"+total); Thread.sleep(500); Bakery.class.notifyAll(); //喚醒消費 } Thread.sleep(500); } } catch (InterruptedException e) { e.printStackTrace(); } } } }
部分打印結果:
阻塞隊列是一個特殊的隊列,也遵循“先進先出”的原則,它是線程安全的隊列結構
特性:典型的生產者消費者模型,一般用于做任務的解耦和消峰
隊列滿的時候,入隊列就堵塞等待(生產),直到有其他線程從隊列中取走元素
隊列空的時候,出隊列就堵塞等待(消費),直到有其他線程往隊列中插入元素
生產者消費者模式就是通過一個容器來解決生產者和消費者的強耦合問題
生產者和消費者彼此之間不直接通信,而通過阻塞隊列來進行通信,所以生產者生產完數據之后等待消費者處理,直接扔給阻塞隊列,消費者不找生產者要數據,而是直接從阻塞隊列里取
阻塞隊列就相當于一個緩沖區,平衡了生產者和消費者的處理能力
阻塞隊列也能使生產者和消費者之間解耦
上述面包房業務的實現就是生產者消費者模型的一個實例
在 Java 標準庫中內置了阻塞隊列, 如果我們需要在一些程序中使用阻塞隊列, 直接使用標準庫中的即可
BlockingQueue 是一個接口. 真正實現的類是 LinkedBlockingQueue
put 方法用于阻塞式的入隊列, take 用于阻塞式的出隊列
BlockingQueue 也有 offer, poll, peek 等方法, 但是這些方法不帶有阻塞特性
BlockingDeque<String> queue = new LinkedBlockingDeque<>(); queue.put("hello"); //如果隊列為空,直接出出隊列就會阻塞 String ret = queue.take(); System.out.println(ret);
這里使用數組實現一個循環隊列來模擬阻塞隊列
當隊列為空的時候,就不能取元素了,就進入wait等待,當有元素存放時,喚醒
當隊列為滿的時候,就不能存元素了,就進入wait等待,當鈾元素取出時,喚醒
實現代碼:
public class MyBlockingQueue { //使用數組實現一個循環隊列,隊列里面存放的是線程要執行的任務 private Runnable[] tasks; //隊列中任務的數量,根據數量來判斷是否可以存取 private int count; private int putIndex; //存放任務位置 private int takeIndex; //取出任務位置 //有參的構造方法,表示隊列容量 public MyBlockingQueue(int size){ tasks = new Runnable[size]; } //存任務 public void put(Runnable task){ try { synchronized (MyBlockingQueue.class){ //如果隊列容量滿了,則存任務等待 while(count == tasks.length){ MyBlockingQueue.class.wait(); } tasks[putIndex] = task; //將任務放入數組 putIndex = (putIndex+1) % tasks.length; //更新存任務位置 count++; //更新存放數量 MyBlockingQueue.class.notifyAll(); //喚醒存任務 } } catch (InterruptedException e) { e.printStackTrace(); } } //取任務 public Runnable take(){ try { synchronized (MyBlockingQueue.class){ //如果隊列任務為空,則取任務等待 while(count==0){ MyBlockingQueue.class.wait(); } //取任務 Runnable task = tasks[takeIndex]; takeIndex = (takeIndex+1) % tasks.length; //更新取任務位置 count--; //更新存放數量 MyBlockingQueue.class.notifyAll(); //喚醒取任務 return task; } } catch (InterruptedException e) { throw new RuntimeException("存放任務出錯",e); } } }
相同點:
都可以讓線程放棄執行一段時間
不同點:
??wait用于線程通信,讓線程在等待隊列中等待
??sleep讓線程阻塞一段時間,阻塞在阻塞隊列中
??wait需要搭配synchronized使用,sleep不用搭配
??wait是Object類的方法,sleep是Thread的靜態方法
以上就是關于“Java中怎么使用wait和notify實現線程間的通信”這篇文章的內容,相信大家都有了一定的了解,希望小編分享的內容對大家有幫助,若想了解更多相關的知識內容,請關注億速云行業資訊頻道。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。