天天看點

java線程第四課:線程的等待通知機制

 在有些時候,我們需要在幾個或多個線程中按照一定的秩序來共享一定的資源。例如生産者--消費者的關系,在這一對關系中實際情況總是先有生産者生産了産品後,消費者才有可能消費;又如在父--子關系中,總是先有父親,然後才能有兒子。然而在沒有引入等待通知機制前,我們得到的情況卻常常是錯誤的。這裡我引入《用線程獲得強大的功能》一文中的生産者--消費者的例子:

/* ==================================================================================
 * 檔案:ThreadDemo07.java
 * 描述:生産者--消費者
 * 注:其中的一些注釋是我根據自己的了解加注的
 * ==================================================================================
 */

// 共享的資料對象
 class ShareData{
  private char c;
  
  public void setShareChar(char c){
   this.c = c;
  }
  
  public char getShareChar(){
   return this.c;
  }
 }
 
 // 生産者線程
 class Producer extends Thread{
  
  private ShareData s;
  
  Producer(ShareData s){
   this.s = s;
  }
  
  public void run(){
   for (char ch = 'A'; ch <= 'Z'; ch++){
    try{
     Thread.sleep((int)Math.random() * 4000);
    }catch(InterruptedException e){}
    
    // 生産
    s.setShareChar(ch);
    System.out.println(ch + " producer by producer.");
   }
  }
 }
 
 // 消費者線程
 class Consumer extends Thread{
  
  private ShareData s;
  
  Consumer(ShareData s){
   this.s = s;
  }
  
  public void run(){
   char ch;
   
   do{
    try{
     Thread.sleep((int)Math.random() * 4000);
    }catch(InterruptedException e){}
    // 消費
    ch = s.getShareChar();
    System.out.println(ch + " consumer by consumer.");
   }while(ch != 'Z');
  }
 }

class Test{
 public static void main(String argv[]){
  ShareData s = new ShareData();
  new Consumer(s).start();
  new Producer(s).start();
 }
}
           

在以上的程式中,模拟了生産者和消費者的關系,生産者在一個循環中不斷生産了從A-Z的共享資料,而消費者則不斷地消費生産者生産的A-Z的共享資料。我們開始已經說過,在這一對關系中,必須先有生産者生産,才能有消費者消費。但如果運作我們上面這個程式,結果卻出現了在生産者沒有生産之前,消費都就已經開始消費了或者是生産者生産了卻未能被消費者消費這種反常現象。為了解決這一問題,引入了等待通知(wait/notify)機制如下: 

  1、在生産者沒有生産之前,通知消費者等待;在生産者生産之後,馬上通知消費者消費。 

  2、在消費者消費了之後,通知生産者已經消費完,需要生産。 

下面修改以上的例子(源自《用線程獲得強大的功能》一文):

/* ==================================================================================
 * 檔案:ThreadDemo08.java
 * 描述:生産者--消費者
 * 注:其中的一些注釋是我根據自己的了解加注的
 * ==================================================================================
 */

class ShareData{
 
 private char c;
 // 通知變量
 private boolean writeable = true;

 // ------------------------------------------------------------------------- 
 // 需要注意的是:在調用wait()方法時,需要把它放到一個同步段裡,否則将會出現
 // "java.lang.IllegalMonitorStateException: current thread not owner"的異常。
 // -------------------------------------------------------------------------
 public synchronized void setShareChar(char c){
  if (!writeable){
   try{
    // 未消費等待
    wait();
   }catch(InterruptedException e){}
  }
  
  this.c = c;
  // 标記已經生産
  writeable = false;
  // 通知消費者已經生産,可以消費
  notify();
 }
 
 public synchronized char getShareChar(){
  if (writeable){
   try{
    // 未生産等待
    wait();
   }catch(InterruptedException e){}  
  }
  // 标記已經消費
  writeable = true;
  // 通知需要生産
  notify();
  return this.c;
 }
}

// 生産者線程
class Producer extends Thread{
 
 private ShareData s;
 
 Producer(ShareData s){
  this.s = s;
 }
 
 public void run(){
  for (char ch = 'A'; ch <= 'Z'; ch++){
   try{
    Thread.sleep((int)Math.random() * 400);
   }catch(InterruptedException e){}
   
   s.setShareChar(ch);
   System.out.println(ch + " producer by producer.");
  }
 }
}

// 消費者線程
class Consumer extends Thread{
 
 private ShareData s;
 
 Consumer(ShareData s){
  this.s = s;
 }
 
 public void run(){
  char ch;
  
  do{
   try{
    Thread.sleep((int)Math.random() * 400);
   }catch(InterruptedException e){}
  
   ch = s.getShareChar();
   System.out.println(ch + " consumer by consumer.**");
  }while (ch != 'Z');
 }
}

class Test{
 public static void main(String argv[]){
  ShareData s = new ShareData();
  new Consumer(s).start();
  new Producer(s).start();
 }
}
           

在以上程式中,設定了一個通知變量,每次在生産者生産和消費者消費之前,都測試通知變量,檢查是否可以生産或消費。最開始設定通知變量為true,表示還未生産,在這時候,消費者需要消費,于時修改了通知變量,調用notify()發出通知。這時由于生産者得到通知,生産出第一個産品,修改通知變量,向消費者發出通知。這時如果生産者想要繼續生産,但因為檢測到通知變量為false,得知消費者還沒有生産,是以調用wait()進入等待狀态。是以,最後的結果,是生産者每生産一個,就通知消費者消費一個;消費者每消費一個,就通知生産者生産一個,是以不會出現未生産就消費或生産過剩的情況。 

繼續閱讀