1. 程式人生 > >Java多執行緒中的虛假喚醒和如何避免

Java多執行緒中的虛假喚醒和如何避免

## 先來看一個例子 一個賣面的麵館,有一個做面的廚師和一個吃麵的食客,需要保證,廚師做一碗麵,食客吃一碗麵,不能一次性多做幾碗面,更不能沒有面的時候吃麵;按照上述操作,進行十輪做面吃麵的操作。 ## 用程式碼說話 首先我們需要有一個資源類,裡面包含面的數量,做面操作,吃麵操作; 當面的數量為0時,廚師才做面,做完面,需要喚醒等待的食客,否則廚師需要等待食客吃完麵才能做面; 當面的數量不為0時,食客才能吃麵,吃完麵需要喚醒正在等待的廚師,否則食客需要等待廚師做完面才能吃麵; 然後在主類中,我們建立一個廚師執行緒進行10次做面,一個食客執行緒進行10次吃麵; 程式碼如下: ```java package com.duoxiancheng.code; /** * @user: code隨筆 */ class Noodles{ //面的數量 private int num = 0; //做面方法 public synchronized void makeNoodles() throws InterruptedException { //如果面的數量不為0,則等待食客吃完麵再做面 if(num != 0){ this.wait(); } num++; System.out.println(Thread.currentThread().getName()+"做好了一份面,當前有"+num+"份面"); //面做好後,喚醒食客來吃 this.notifyAll(); } //吃麵方法 public synchronized void eatNoodles() throws InterruptedException { //如果面的數量為0,則等待廚師做完面再吃麵 if(num == 0){ this.wait(); } num--; System.out.println(Thread.currentThread().getName()+"吃了一份面,當前有"+num+"份面"); //吃完則喚醒廚師來做面 this.notifyAll(); } } public class Test { public static void main(String[] args) { Noodles noodles = new Noodles(); new Thread(new Runnable(){ @Override public void run() { try { for (int i = 0; i < 10 ; i++) { noodles.makeNoodles(); } } catch (InterruptedException e) { e.printStackTrace(); } } },"廚師A").start(); new Thread(new Runnable(){ @Override public void run() { try { for (int i = 0; i < 10 ; i++) { noodles.eatNoodles(); } } catch (InterruptedException e) { e.printStackTrace(); } } },"食客甲").start(); } } ``` 輸出如下: ![](https://img-blog.csdnimg.cn/20201203165128214.png) 可以見到是交替輸出的; ## 如果有兩個廚師,兩個食客,都進行10次迴圈呢? Noodles類的程式碼不用動,在主類中多建立兩個執行緒即可,主類程式碼如下: ```java public class Test { public static void main(String[] args) { Noodles noodles = new Noodles(); new Thread(new Runnable(){ @Override public void run() { try { for (int i = 0; i < 10 ; i++) { noodles.makeNoodles(); } } catch (InterruptedException e) { e.printStackTrace(); } } },"廚師A").start(); new Thread(new Runnable(){ @Override public void run() { try { for (int i = 0; i < 10 ; i++) { noodles.makeNoodles(); } } catch (InterruptedException e) { e.printStackTrace(); } } },"廚師B").start(); new Thread(new Runnable(){ @Override public void run() { try { for (int i = 0; i < 10 ; i++) { noodles.eatNoodles(); } } catch (InterruptedException e) { e.printStackTrace(); } } },"食客甲").start(); new Thread(new Runnable(){ @Override public void run() { try { for (int i = 0; i < 10 ; i++) { noodles.eatNoodles(); } } catch (InterruptedException e) { e.printStackTrace(); } } },"食客乙").start(); } } ``` 此時輸出如下: ![在這裡插入圖片描述](https://img-blog.csdnimg.cn/20201203165658414.png) ## 虛假喚醒 上面的問題就是"虛假喚醒"。 當我們只有一個廚師一個食客時,只能是廚師做面或者食客吃麵,並沒有其他情況; 但是當有兩個廚師,兩個食客時,就會出現下面的問題: 1. 初始狀態 ![](https://img-blog.csdnimg.cn/20201203171644506.png) 2. 廚師A得到操作權,發現面的數量為0,可以做面,面的份數+1,然後喚醒所有執行緒; ![](https://img-blog.csdnimg.cn/20201203172159931.png) 3. 廚師B得到操作權,發現面的數量為1,不可以做面,執行wait操作; ![](https://img-blog.csdnimg.cn/20201203172354406.png) 4. 廚師A得到操作權,發現面的數量為1,不可以做面,執行wait操作; ![](https://img-blog.csdnimg.cn/20201203172629233.png) 5. 食客甲得到操作權,發現面的數量為1,可以吃麵,吃完麵後面的數量-1,並喚醒所有執行緒; ![](https://img-blog.csdnimg.cn/20201203173145435.png) 6. 此時廚師A得到操作權了,因為是從剛才阻塞的地方繼續執行,就不用再判斷面的數量是否為0了,所以直接面的數量+1,並喚醒其他執行緒; ![](https://img-blog.csdnimg.cn/20201203173456668.png) 7. 此時廚師B得到操作權了,因為是從剛才阻塞的地方繼續執行,就不用再判斷面的數量是否為0了,所以直接面的數量+1,並喚醒其他執行緒; ![](https://img-blog.csdnimg.cn/20201203173715967.png) 這便是虛假喚醒,還有其他的情況,讀者可以嘗試畫畫圖分析分析。 ## 解決方法 出現虛假喚醒的原因是從阻塞態到就緒態再到執行態沒有進行判斷,我們只需要讓其每次得到操作權時都進行判斷就可以了; 所以將 ```java if(num != 0){ this.wait(); } ``` 改為 ```java while(num != 0){ this.wait(); } ``` 將 ```java if(num == 0){ this.wait(); } ``` 改為 ```java while(num == 0){ this.wait(); } ``` 即可。 >微信搜尋:code隨筆 歡迎關注樂於輸出Java,演算法等乾貨的技術公