Java多線程學習(七)

等待/通知模式中最經典的案例當屬生產者/消費者模式,此模式有幾種變形,但都是基於wait/notify的。java

生產者/消費者模式有兩種類型:操做值的和操做棧的。下面分別從這兩方面來說解生產者/消費者模式。this

  1. 操做值線程

    ①一輩子產與一消費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方法。產品

     

  2. 操做棧it

    ①一輩子產與一消費io

    public class MyStack {
           private List list = new ArrayList();     //操做棧
  3.        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();     //操做棧
  4.        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();
             }
    }
    運行結果:此次不會拋出異常,頁不會出現假死狀況。程序會正常運行下去。
  5. 註明:最後一種while + notifyAll的類型一樣適用於多生產者和一消費者,以及多生產者和多消費者這兩種狀況。

相關文章
相關標籤/搜索