等待/通知模式中最經典的案例當屬生產者/消費者模式,此模式有幾種變形,但都是基於wait/notify的。java
生產者/消費者模式有兩種類型:操做值的和操做棧的。下面分別從這兩方面來說解生產者/消費者模式。this
操做值線程
①一輩子產與一消費code
public class P { private String lock; //操做值 public P(String lock){ this.lock = lock; } public void setValue(){ synchronized(lock){ if(!ValueObject.value.equals("")){ lock.wait(); } String value = System.currentTimeMillis(); ValueObject.value = value; lock.notify(); } } } public class C { private String lock; //操做值 public C(String lock){ this.lock = lock; } public void getValue(){ synchronized(lock){ if(ValueObject.value.equals("")){ lock.wait(); } ValueObject.value = ""; lock.notify(); } } } public class ValueObject { public static String value = ""; } public class ThreadP extends Thread { private P p; public ThreadP(P p){ this.p = p; } public void run(){ while(true){ p.setValue(); } } } public class ThreadC extends Thread { private C c; public ThreadC(C c){ this.c = c; } public void run(){ while(true){ c.getValue(); } } } public class Run{ public static void main(String [] args){ String lock = new String(""); P p = new P(lock); C c = new C(lock); ThreadP tp = new ThreadP(p); ThreadC tc = new ThreadC(c); tp.start(); tc.start(); } } 運行結果:不斷地循環運行生產者的setValue方法和消費者的getValue方法,不停的交換數據。
分析:tp線程首先運行,lock == 「」,生產者p發現還沒有生產,因此先生產,在下一次循環中發現value != 「」;即生產出的產品還沒有被消費,因此先等待進入wait狀態。tc線程開始運行,發現 value!=「」,產品尚未被消費,因此先消費,在下一次循環中發現 value == 「」,即生產者尚未生產,因此進入wait狀態,讓出控制權,讓生產者繼續生產...進入一種無限循環中。進程
②多生產與多消費:操做值--有可能進入假死狀態rem
public class P { private String lock; //操做值 public P(String lock){ this.lock = lock; } public void setValue(){ synchronized(lock){ while(!ValueObject.value.equals("")){ lock.wait(); } String value = System.currentTimeMillis(); ValueObject.value = value; lock.notify(); } } } public class C { private String lock; //操做值 public C(String lock){ this.lock = lock; } public void getValue(){ synchronized(lock){ while(ValueObject.value.equals("")){ lock.wait(); } ValueObject.value = ""; lock.notify(); } } } public class ValueObject { public static String value = ""; } public class ThreadP extends Thread { private P p; public ThreadP(P p){ this.p = p; } public void run(){ while(true){ p.setValue(); } } } public class ThreadC extends Thread { private C c; public ThreadC(C c){ this.c = c; } public void run(){ while(true){ c.getValue(); } } } public class Run{ public static void main(String [] args){ String lock = new String(""); P p = new P(lock); C c = new C(lock); ThreadP tp1 = new ThreadP(p); ThreadP tp2 = new ThreadP(p); ThreadC tc1 = new ThreadC(c); ThreadC tc1 = new ThreadC(c); tp1.start(); tc1.start(); tp2.start(); tc2.start(); } } 運行結果:極有可能出現假死狀況,即四個線程都處於wait狀態。
分析:首先生產者1在檢查了條件,即value==「」後開始生產,而後調用notify方法,在下一次while循環檢查條件是發現value != 「」;進入wait狀態;下一次若被生產者2搶到控制權,發現已經生產,則生產者2直接進入wait狀態。而後消費者1檢查發現value != 「」,開始消費,在下一次while循環中,發現value == 「」,即還沒有生產,則進入wait狀態,這時若消費者2搶到了鎖, 也發現還沒有生產,則也進入wait狀態;生產者1又一次搶到了鎖,開始生產,在下一次while循環中,發現還沒有消費,進入了wait狀態;此時若又由生產者2拿到了鎖,它發現還沒有消費,則也進入wait狀態。如今,四個進程都在等待,進入了假死狀態。get
「假死」狀態是由於線程連續喚醒同類形成的,生產者1喚醒生產者2,消費者1喚醒消費者2。如何解決?不只喚醒同類,並且將異類也一塊兒喚醒。即將生產者和消費者中的notify方法換爲notifyAll方法。產品
操做棧it
①一輩子產與一消費io
public class MyStack { private List list = new ArrayList(); //操做棧
synchronized public void push(){ if(list.size() == 1){ this.wait(); } list.add("str"); this.notify(); } synchronized public void pop(){ if(list.size() == 0){ this.wait(); } list.remove(0); this.notify(); } } public class P { private MyStack myStack; public P(MyStack s){ this.myStack = s; } public void pushService(){ myStack.push(); } } public class C { private MyStack myStack; public C(MyStack s){ this.myStack = s; } public void popService(){ myStack.pop(); } } public class ThreadP extends Thread { private P p; public ThreadP(P p){ this.p = p; } public void run(){ while(true){ p.pushService(); } } } public class ThreadC extends Thread { private C c; public ThreadC(C c){ this.c = c; } public void run(){ while(true){ c.popService(); } } } public class Run{ public static void main(String [] args){ MyStack muStack= new MyStack(); P p = new P(myStack); C c = new C(myStack); ThreadP tp = new ThreadP(p); ThreadC tc = new ThreadC(c); tp.start(); tc.start(); } } 運行結果:size不會超過1,生產和消費過程在輪流執行。
分析:生產者首先運行,size==0,首先生產,在下一次while循環是進入wait狀態;消費者運行,首先消費,在下一次while循環時發現還未生產,進入wait狀態。喚醒生產者進行生產。。。不斷循環此過程。
②一輩子產和多消費---可能遭遇條件改變拋出的異常和假死狀況
上例的代碼不進行更改,只修改運行類以下: public class Run{ public static void main(String [] args){ MyStack muStack= new MyStack(); P p = new P(myStack); C c1 = new C(myStack); C c2 = new C(myStack); C c3 = new C(myStack); C c4 = new C(myStack); C c5 = new C(myStack); ThreadP tp = new ThreadP(p); ThreadC tc1 = new ThreadC(c1); ThreadC tc2 = new ThreadC(c2); ThreadC tc3 = new ThreadC(c3); ThreadC tc4 = new ThreadC(c4); ThreadC tc5 = new ThreadC(c5); tp.start(); tc.start(); } } 運行結果:在某些概率下出現異常IndexOutOfBoundException。
分析:p1進行生產後進入wait狀態;c1開始消費,消費後進入wait狀態,假設此時消費者c二、c三、c四、c5都進入wait狀態。p1有開始生產,而後喚醒c2進行消費後又進入wait狀態,而後喚醒c3,因爲代碼中是if結構,從wait中醒來時不會再檢查條件,而是直接進行remove操做,這時拋出異常。
這是由於條件改變後沒有獲得即時的響應,解決這個問題的方法是,將if改成while語句便可;
public class MyStack { private List list = new ArrayList(); //操做棧 synchronized public void push(){ while(list.size() == 1){ this.wait(); } list.add("str"); this.notify(); } synchronized public void pop(){ while(list.size() == 0){ this.wait(); } list.remove(0); this.notify(); } } 運行結果:此次不會拋出異常,可是出現假死狀況。
分析:由於是while循環,那麼消費者在檢查條件後,優惠進入wait狀態,這是生產者和全部的消費者都進入了wait狀態。
解決方法:將notify()方法改成notifyAll()方法便可。
public class MyStack { private List list = new ArrayList(); //操做棧
synchronized public void push(){ while(list.size() == 1){ this.wait(); } list.add("str"); this.notifyAll(); } synchronized public void pop(){ while(list.size() == 0){ this.wait(); } list.remove(0); this.notifyAll(); } } 運行結果:此次不會拋出異常,頁不會出現假死狀況。程序會正常運行下去。
註明:最後一種while + notifyAll的類型一樣適用於多生產者和一消費者,以及多生產者和多消費者這兩種狀況。