生產者和消費者案例是多線程通訊中一個經典案例。如今Buffer裏邊有初始值爲0的產品,要求生產者沒生產一個產品,消費者就消費一個產品。這是一個簡單的案例,使用多線程的等待喚醒機制就能輕鬆解決。java
生產者消費者編碼實現多線程
class Buffer{ private int num = 0; boolean flag = true; public void increase(){ synchronized (this){ if(!flag){ try { this.wait(); } catch (Exception e) { e.printStackTrace(); } } num++; flag = false; System.out.println(Thread.currentThread().getName()+" : "+num); this.notifyAll(); } } public void decrease(){ synchronized (this){ if(flag){ try { this.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } num--; flag = true; System.out.println(Thread.currentThread().getName()+" : "+num); this.notifyAll(); } } } public class ThreadCommunication { public static void main(String[] args) { Buffer buffer = new Buffer(); new Thread(()->{ for (int i = 0; i < 10; i++) { buffer.increase(); } },"AAA").start(); new Thread(()->{ for (int i = 0; i < 10; i++) { buffer.decrease(); } },"BBB").start(); } } AAA : 1 BBB : 0 AAA : 1 BBB : 0 AAA : 1 BBB : 0 AAA : 1 BBB : 0 AAA : 1 BBB : 0
以上代碼在只有一個生產者一個消費者的兩個線程中沒有什麼問題,可是生產者消費者一旦多起來就可能產生問題。如今要求兩個生產者生產,兩個消費者消費。這樣就可能產生虛假喚醒的問題。this
class Buffer{ private int num; boolean flag = true; public void increase(){ synchronized (this){ if(!flag){ try { this.wait(); } catch (Exception e) { e.printStackTrace(); } } num++; flag = false; System.out.println(Thread.currentThread().getName()+" : "+num); this.notifyAll(); } } public void decrease(){ synchronized (this){ if(flag){ try { this.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } num--; flag = true; System.out.println(Thread.currentThread().getName()+" : "+num); this.notifyAll(); } } } public class ThreadCommunication { public static void main(String[] args) { Buffer buffer = new Buffer(); new Thread(()->{ for (int i = 0; i < 5; i++) { buffer.increase(); } },"AAA").start(); new Thread(()->{ for (int i = 0; i < 5; i++) { buffer.decrease(); } },"BBB").start(); new Thread(()->{ for (int i = 0; i < 5; i++) { buffer.increase(); } },"CCC").start(); new Thread(()->{ for (int i = 0; i < 5; i++) { buffer.decrease(); } },"DDD").start(); } } AAA : 1 BBB : 0 AAA : 1 BBB : 0 AAA : 1 BBB : 0 AAA : 1 BBB : 0 CCC : 1 AAA : 2 CCC : 3 DDD : 2 BBB : 1 DDD : 0 CCC : 1 DDD : 0 CCC : 1 DDD : 0 CCC : 1 DDD : 0
出現問題CCC : 1 AAA : 2 CCC : 3 這就是虛假喚醒問題編碼
虛假喚醒產生的緣由:在上面的Demo中有AC兩個生產者和BD兩個消費者。不管是生產者仍是消費者每次生產或者消費一個產品以後都要喚醒全部的阻塞線程再次競爭共享資源Buffer。若是wait前面的判斷條件用的是if就可能存在下面一種情景:生產者已經生產一件產品(num = 1),flag=flase。此時就會喚醒全部的ABCD四個線程,線程
生產者A得到Buffer的鎖對象,進入判斷髮現flag==false就會阻塞。code
生產者C得到Buffer的鎖對象,進入判斷髮現flag==false對象也會阻塞。對象
消費者B得到Buffer的鎖對象,進入判斷flag=flase,正常執行完畢喚醒全部的阻塞線程,將flag設置tureblog
生產者A得到Buffer的鎖對象,因爲阻塞的時候已經用if判斷過flag,線程被喚醒的時候直接從阻塞的地方開始執行,喚醒全部阻塞線程,將flag設置flase資源
生產者A得到Buffer的鎖對象,因爲阻塞的時候已經用if判斷過flag,喚醒時候從阻塞地方開始執行。雖然flag爲false,可是不會從新判斷不會致使當前線程阻塞。致使num連續自增兩次。get
從上面能夠看出形成虛假喚醒的緣由就是阻塞線程被喚醒的時候要從中斷地方開始執行,若是用if作wait的判斷條件會致使喚醒以後不加判斷直接執行致使程序出錯。所以解決方案就是使用while替代if做爲判斷條件。即便是阻塞以後喚醒也要從新判斷。官方的API也對這個問題有說明
總結:多線程使用等待喚醒機制要防止虛假喚醒問題,wait前面的判斷條件不能使用if要使用while
class Buffer{ private int num; boolean flag = true; public void increase(){ synchronized (this){ //wait的判斷條件爲了防止虛假喚醒要使用while作判斷 while(!flag){ try { this.wait(); } catch (Exception e) { e.printStackTrace(); } } num++; flag = false; System.out.println(Thread.currentThread().getName()+" : "+num); this.notifyAll(); } } public void decrease(){ synchronized (this){ //爲了防止虛假喚醒要使用while作判斷 if(flag){ try { this.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } num--; flag = true; System.out.println(Thread.currentThread().getName()+" : "+num); this.notifyAll(); } } } public class ThreadCommunication { public static void main(String[] args) { Buffer buffer = new Buffer(); new Thread(()->{ for (int i = 0; i < 5; i++) { buffer.increase(); } },"AAA").start(); new Thread(()->{ for (int i = 0; i < 5; i++) { buffer.decrease(); } },"BBB").start(); new Thread(()->{ for (int i = 0; i < 5; i++) { buffer.increase(); } },"CCC").start(); new Thread(()->{ for (int i = 0; i < 5; i++) { buffer.decrease(); } },"DDD").start(); } } AAA : 1 BBB : 0 AAA : 1 BBB : 0 AAA : 1 BBB : 0 AAA : 1 BBB : 0 AAA : 1 BBB : 0 CCC : 1 DDD : 0 CCC : 1 DDD : 0 CCC : 1 DDD : 0 CCC : 1 DDD : 0 CCC : 1 DDD : 0