先看關於wait()方法的javadoc描述java
Causes the current thread to wait until another thread invokes the notify() method or the notifyAll() method for this object. In other words, this method behaves exactly as if it simply performs the call wait(0). The current thread must own this object's monitor. The thread releases ownership of this monitor and waits until another thread notifies threads waiting on this object's monitor to wake up either through a call to the notify method or the notifyAll method. The thread then waits until it can re-obtain ownership of the monitor and resumes execution. As in the one argument version, interrupts and spurious wakeups are possible, and this method should always be used in a loop: 譯文: 線程會等待,直到另外一個線程執行這個對象的notify或notifyAll 方法,換句話說就是此方法至關於執行了wait(0)方法, 此方法執行是當前線程必須擁有此對象的監視器(不然會報IllegalMonitorStateException 異常), 當前線程會釋放 此對象監視的全部權, 並等待直到另外一個線程經過調用notify或notifyAll方法通知此等待對象監視器的線程喚醒,而後線程等待, 直到它從新獲取到監視器全部權並繼續執行。和單參數版本同樣, 中斷和虛假喚醒是可能的,而且該方法應始終在循環中使用。
而最後一句話即是本篇文章的重點:虛假喚醒。先看例子:ide
消費方法oop
public synchronized void customer(){//消費 if(product <= 0){ System.out.println(Thread.currentThread().getName()+",產品缺貨:"); try { System.out.println("cus1111"); this.wait(); System.out.println("cus2222"); } catch (InterruptedException e) { e.printStackTrace(); } }else{ System.out.println(Thread.currentThread().getName()+",消費了一個產品:"+ product--); this.notifyAll(); } }
生成方法this
public synchronized void product(){//生產 if(product >= 1){ System.out.println(Thread.currentThread().getName()+",產品滿了:"); try { this.wait(); } catch (InterruptedException e) { e.printStackTrace(); } }else{ System.out.println(Thread.currentThread().getName()+",生產了一個產品:"+ ++product); this.notifyAll(); } }
消費次數和生成次數是同樣的前提下,存在這樣一種狀況:線程
生成者還有兩次執行權,消費者也有兩次。假設此時產品數爲0,生產者搶到了cpu執行權,進行了一次生產,而後生成者有搶到了cpu執行權,執行了一次,並等待在那了,以後生產者能夠執行的次數爲0了。code
接着消費者獲得了cpu執行權,進行了一次消費,並喚醒了等待中的生產者。以後假設消費者搶到了cpu執行權,就先進行消費,此時produce爲0,消費者等待在那了,此時生產者獲得了cpu執行權,從wait方法往下執行,不會執行notifyAll方法,等待中的消費者沒法被喚醒,而後生產者線程就退出了,無人再來喚醒消費者線程了。orm
結果以下,程序沒法正常退出對象
解決辦法:將else去掉。這樣就能在生產者被喚醒後,執行notifyAll方法,從而喚醒等待中的消費者。但這樣是存在問題的,什麼問題呢?那就是本篇文章的主角:虛假喚醒。blog
咱們先在生產者線程誰200毫秒,模擬業務操做,並開啓兩個生產者,兩個消費者,同時對Clerk 的產品 進行操做,主要代碼以下;ip
Clerk clerk=new Clerk(); new Thread(new Prod(clerk), "prod-1").start(); new Thread(new Customer(clerk),"cus-1").start(); new Thread(new Prod(clerk), "prod-2").start(); new Thread(new Customer(clerk),"cus-2").start(); --------------------------------------------------------------- @Override public void run() { for (int i = 0; i < 10; i++) { try { Thread.sleep(200); } catch (InterruptedException e) { e.printStackTrace(); } clerk.prod(); } }
結果:程序能正常退出,但卻產生了負數產品
緣由:一旦兩個消費者同時進入了wait方法,同時釋放了cpu執行權,而後由生產者進行notifyAll 進行喚醒,兩個消費者線程同時對produce 進行消費,因此才產生了負數產品。
解決:根據wait的javadoc建議,將wait() 放在循環裏進行操做便可,這樣當兩個消費者同時被喚醒時,就會再斷定一次,從而不會產生虛假喚醒問題。
最後附上完整的代碼
public class WatiTest { public static void main(String[] args) { Clerk clerk=new Clerk(); new Thread(new Prod(clerk), "prod-1").start(); new Thread(new Customer(clerk),"cus-1").start(); new Thread(new Prod(clerk), "prod-2").start(); new Thread(new Customer(clerk),"cus-2").start(); } } class Clerk{ int produce=0; public synchronized void prod(){ if(produce >= 1){ System.out.println(Thread.currentThread().getName()+",滿了"); try { this.wait(); System.out.println(Thread.currentThread().getName()+",被喚醒"); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println(Thread.currentThread().getName()+",生產了一個產品:"+ ++produce); this.notifyAll(); } public synchronized void sale(){ if(produce <= 0){ System.out.println(Thread.currentThread().getName()+",缺貨"); try { this.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println(Thread.currentThread().getName()+",消費了一個產品:"+ produce--); this.notifyAll(); } } class Prod implements Runnable{ Clerk clerk; public Prod(Clerk clerk) { this.clerk = clerk; } @Override public void run() { for (int i = 0; i < 10; i++) { try { Thread.sleep(200); } catch (InterruptedException e) { e.printStackTrace(); } clerk.prod(); } } } class Customer implements Runnable{ Clerk clerk; public Customer(Clerk clerk) { this.clerk = clerk; } @Override public void run() { for (int i = 0; i < 10; i++) { clerk.sale(); } } }