您好,登錄后才能下訂單哦!
本篇文章給大家分享的是有關怎么在Java多線程中使用wait、notify、notifyAll,小編覺得挺實用的,因此分享給大家學習,希望大家閱讀完這篇文章后可以有所收獲,話不多說,跟著小編一起來看看吧。
基礎知識
首先我們需要知道,這幾個都是Object對象的方法。換言之,Java中所有的對象都有這些方法。
public final native void notify(); public final native void notifyAll(); public final native void wait(long timeout) throws InterruptedException; public final void wait() throws InterruptedException { wait(0); }
其中notify()、notifyAll()、wait(long timeout)是本地方法
其次我們需要知道這幾個方法主要是用來個線程之間通信的。那可能就有人會問,既然是用來線程之間通信的,那為什么這幾個方法不是在線程類Thread上呢?對于這個問題,我們先來看下這幾個方法的具體使用方式再來回答這個問題。
用法
Java中規定,在調用者三個方法時,當前線程必須獲得對象鎖。因此就得配合synchronized關鍵字來使用
//使用模式,不代表可運行代碼 synchronized(object) { while(contidion) { object.wait(); } //object.notify(); //object.notifyAll(); }
或者
public synchronized void methodName() { while(contidion) { object.wait(); } //object.notify(); //object.notifyAll(); }
在synchronized拿到對象鎖之后,synchronized代碼塊或者方法中,必定是會持有對象鎖的,因此就可以使用wait()或者notify()。
通過上述使用方法,我們也能很好理解為什么這幾個方法是在Object上而不是在Thread上。因為每個對象都可以作為synchronized鎖的對象,因此wait、notify等必須和對象關聯才能配合synchronized使用。
作用
方法 | 作用 |
---|---|
wait | 線程自動釋放占有的對象鎖,并等待notify。 |
notify | 隨機喚醒一個正在wait當前對象的線程,并讓被喚醒的線程拿到對象鎖 |
notifyAll | 喚醒所有正在wait當前對象的線程,但是被喚醒的線程會再次去競爭對象鎖。因為一次只有一個線程能拿到鎖,所有其他沒有拿到鎖的線程會被阻塞。推薦使用。 |
1、wait()、notify/notifyAll() 方法是Object的本地final方法,無法被重寫。
2、wait()使當前線程阻塞,前提是 必須先獲得鎖,一般配合synchronized 關鍵字使用,即,一般在synchronized 同步代碼塊里使用 wait()、notify/notifyAll() 方法。
3、 由于 wait()、notify/notifyAll() 在synchronized 代碼塊執行,說明當前線程一定是獲取了鎖的。
當線程執行wait()方法時候,會釋放當前的鎖,然后讓出CPU,進入等待狀態。
只有當 notify/notifyAll() 被執行時候,才會喚醒一個或多個正處于等待狀態的線程,然后繼續往下執行,直到執行完synchronized 代碼塊的代碼或是中途遇到wait() ,再次釋放鎖。
也就是說,notify/notifyAll() 的執行只是喚醒沉睡的線程,而不會立即釋放鎖,鎖的釋放要看代碼塊的具體執行情況。所以在編程中,盡量在使用了notify/notifyAll() 后立即退出臨界區,以喚醒其他線程
4、wait() 需要被try catch包圍,中斷也可以使wait等待的線程喚醒。
5、notify 和wait 的順序不能錯,如果A線程先執行notify方法,B線程在執行wait方法,那么B線程是無法被喚醒的。
6、notify 和 notifyAll的區別
notify方法只喚醒一個等待(對象的)線程并使該線程開始執行。所以如果有多個線程等待一個對象,這個方法只會喚醒其中一個線程,選擇哪個線程取決于操作系統對多線程管理的實現。notifyAll 會喚醒所有等待(對象的)線程,盡管哪一個線程將會第一個處理取決于操作系統的實現。如果當前情況下有多個線程需要被喚醒,推薦使用notifyAll 方法。比如在生產者-消費者里面的使用,每次都需要喚醒所有的消費者或是生產者,以判斷程序是否可以繼續往下執行。
7、在多線程中要測試某個條件的變化,使用if 還是while?
要注意,notify喚醒沉睡的線程后,線程會接著上次的執行繼續往下執行。所以在進行條件判斷時候,可以先把 wait 語句忽略不計來進行考慮,顯然,要確保程序一定要執行,并且要保證程序直到滿足一定的條件再執行,要使用while來執行,以確保條件滿足和一定執行。如下代碼:
public class K { //狀態鎖 private Object lock; //條件變量 private int now,need; public void produce(int num){ //同步 synchronized (lock){ //當前有的不滿足需要,進行等待 while(now < need){ try { //等待阻塞 wait(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("我被喚醒了!"); } // 做其他的事情 } } }
顯然,只有當前值滿足需要值的時候,線程才可以往下執行,所以,必須使用while 循環阻塞。注意,wait() 當被喚醒時候,只是讓while循環繼續往下走.如果此處用if的話,意味著if繼續往下走,會跳出if語句塊。但是,notifyAll 只是負責喚醒線程,并不保證條件云云,所以需要手動來保證程序的邏輯。
實際案例
接下來我們就使用wait()、notify()來實現一個生產者、消費者模式。這個也是面試過程中可能會被問到的地方。至于什么是生產者消費者模式,不明白的同學請自行百度。
首先是一些基礎的代碼
private static Boolean run = true;//控制是否生產和消費 private static final Integer MAX_CAPACITY = 5;//緩沖區最大數量 private static final LinkedBlockingQueue<String> queue = new LinkedBlockingQueue<>();//緩沖隊列
生產者代碼
/** * 生產者 */ class Producter extends Thread { @Override public void run() { while (run) { synchronized (queue) { while (queue.size() >= MAX_CAPACITY * 2) { try { System.out.println("緩沖隊列已滿,等待消費"); queue.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } try { String string = UUID.randomUUID().toString(); queue.put(string); System.out.println("生產:" + string); Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } queue.notifyAll();//通知生產者和消費者 } } } }
消費者代碼
/** * 消費者 */ class Consumer extends Thread { @Override public void run() { while (run) { synchronized (queue) { while (queue.isEmpty()) { try { System.out.println("隊列為空,等待生產"); queue.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } try { System.out.println("消費:" + queue.take()); Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } queue.notifyAll();//通知生產者和消費者 } } } }
代碼說明
1、生產者和消費者都繼承了線程Thread,因為wait、notify本身就是線程間通信使用
2、生產者和消費者都有兩層while,外層的while是用來判斷是否運行生產者和消費者。內存的while用來判斷隊列queue是否已滿或者為空,如果滿足條件,則使得當前線程變成等待狀態(等待notify)。
3、內層的條件判斷為什么用while不用if,原因是當線程被wait之后,會釋放對象鎖。當等待的線程被notify之后,必須再次嘗試去獲取對象鎖,如果沒有獲取到對象鎖,那還必須等待,直到拿到對象鎖之后才能向后執行。
4、當生產者生產了一個數據或者消費者消費了一個數據之后,使用notifyAll()方法來通知所有等待當前對象鎖的線程,但是一次只會有一個等待的線程能拿到鎖。
5、我們使用queue作為鎖的對象在不同線程之間進行通信
代碼運行結果
隊列為空,等待生產
生產:e422484e-8eb3-4a91-8a7c-97e21d7ef498
生產:7894b802-2529-4798-ba98-9f0657f39240
生產:848f6759-a427-4a94-89dc-3f484daaa467
生產:f711d3dc-972c-4c44-8640-faffe376d354
生產:38a08e62-d774-4ed5-8b51-f1ad534c246f
消費:e422484e-8eb3-4a91-8a7c-97e21d7ef498
消費:7894b802-2529-4798-ba98-9f0657f39240
消費:848f6759-a427-4a94-89dc-3f484daaa467
消費:f711d3dc-972c-4c44-8640-faffe376d354
消費:38a08e62-d774-4ed5-8b51-f1ad534c246f
隊列為空,等待生產
生產:9fae26f3-0b6e-4fbd-9620-040667efe0af
生產:95bb1d88-e08a-4f70-a270-f75760994184
消費:9fae26f3-0b6e-4fbd-9620-040667efe0af
消費:95bb1d88-e08a-4f70-a270-f75760994184
隊列為空,等待生產
生產:13d304bc-dff3-44a4-9527-2e0facd884e7
生產:2693e069-bae1-4beb-adcd-a3c3bf5d232b
消費:13d304bc-dff3-44a4-9527-2e0facd884e7
消費:2693e069-bae1-4beb-adcd-a3c3bf5d232b
以上就是怎么在Java多線程中使用wait、notify、notifyAll,小編相信有部分知識點可能是我們日常工作會見到或用到的。希望你能通過這篇文章學到更多知識。更多詳情敬請關注億速云行業資訊頻道。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。