wait()與notify()

  • 2019 年 10 月 3 日
  • 筆記

一,前言

​ 簡單畫了一下執行緒的流程圖,只是一個大概。如圖所示,執行緒有多種狀態,那麼不同狀態之間是如何切換的,下面主要總結關於wait()和notify()的使用。

二,wait()

​ wait()和notify()都是定義在Object類中,為什麼如此設計。因為synchronized中的這把鎖可以是任意對象,所以任意對象都可以調用wait()和notify(),並且只有同一把鎖才能對執行緒進行操作,不同鎖之間是不可以相互操作的,所以wait和notify屬於Object。請看如下API文檔說明。

​ wait()提供三種構造方法,但前兩種最為常用,wait()是讓執行緒一直處於等待狀態,直到手動喚醒,而wait(long timeout)可以指定等待時間,之後會自動喚醒。

​ 調用wait方法可以讓當前執行緒進入等待喚醒狀態,該執行緒會處於等待喚醒狀態直到另一個執行緒調用了object對象的notify方法或者notifyAll方法。

三,notify()

​ notify()喚醒等待的執行緒,如果監視器種只有一個等待執行緒,使用notify()可以喚醒。但是如果有多條執行緒notify()是隨機喚醒其中一條執行緒,與之對應的就是notifyAll()就是喚醒所有等待的執行緒,請看下面實例程式碼。

​ 案例:定義兩條執行緒,分別讓其執行緒等待,及執行緒喚醒。

​ 1,定義執行緒。

public class SetTarget implements Runnable{      private Demo demo;      public SetTarget(Demo demo) {          this.demo = demo;      }      @Override      public void run() {          demo.set();      }  }
public class GetTarget implements Runnable {      private Demo demo;      public GetTarget(Demo demo) {          this.demo = demo;      }      @Override      public void run() {          demo.get();      }  }

​ 2,編寫main方法。

public class Demo {        // 定義一個訊號量      private volatile int signal;        public static void main(String[] args) {            Demo demo = new Demo();          SetTarget set = new SetTarget(demo);          GetTarget get = new GetTarget(demo);            // 開啟執行緒,          new Thread(get).start();          new Thread(get).start();          new Thread(get).start();          new Thread(get).start();            try {              TimeUnit.SECONDS.sleep(1);          } catch (InterruptedException e) {              e.printStackTrace();          }            new Thread(set).start();      }        // set方法喚醒執行緒      public synchronized void set() {          signal = 1;          // notify方法會隨機叫醒一個處於wait狀態的執行緒          notify();          // notifyAll叫醒所有的處於wait執行緒,爭奪到時間片的執行緒只有一個          //notifyAll();          System.out.println("叫醒執行緒叫醒之後休眠開始...");          try {              Thread.sleep(3000);          } catch (InterruptedException e) {              e.printStackTrace();          }      }        // get方法使執行緒進入等待狀態      public synchronized int get() {          System.out.println(Thread.currentThread().getName() + " 方法執行了...");          if (signal != 1) {              try {                  wait();                  System.out.println("叫醒之後");              } catch (InterruptedException e) {                  e.printStackTrace();              }          }          System.out.println(Thread.currentThread().getName() + " 方法執行完畢...");          return signal;      }  }  

​ 3,運行結果。

​ 分析:一共開啟了4個執行緒,當全部進入等待狀態時,調用notify()方法喚醒執行緒,但很明顯只喚醒了其中一條執行緒。右上角顯示程式並沒有停止,原因就是其他3條執行緒仍在處於等待狀態。

​ 使用notifyAll()喚醒執行緒:

四,生產者-消費者模式

​ 生產者-消費者模式,生產者生產商品,然後通知消費者進行消費。

​ 1,定義生產者

public class Vendor {      // 定義庫存數量      private int count;        // 定義最大庫存      private final int MAX_COUNT = 10;        public synchronized void production() {          while (count >= MAX_COUNT) {              try {                  System.out.println(Thread.currentThread().getName() + "庫存數量達到最大值,停止生產。");                  // 此時生產執行緒全部進入等待狀態                  wait();              } catch (InterruptedException e) {                  e.printStackTrace();              }          }          // 否則生產商品          count++;          System.out.println(Thread.currentThread().getName() + "正在生產商品,當前庫存為:" + count);          notifyAll();        }        public synchronized void consumers() {          while (count <= 0) {              try {                  System.out.println(Thread.currentThread().getName() + "沒有商品了,消費者處於等待狀態...");                  wait();              } catch (InterruptedException e) {                  e.printStackTrace();              }          }          count--;          System.out.println(Thread.currentThread().getName() + "正在消費,當前庫存為:" + count);          notifyAll();      }  }  

​ 2,分別定義兩條執行緒。

public class SetTarget implements Runnable {        private Vendor vendor;        public SetTarget(Vendor vendor) {          this.vendor = vendor;      }        @Override      public void run() {          while(true){              vendor.production();              try {                  Thread.sleep(1000);              } catch (InterruptedException e) {                  // TODO Auto-generated catch block                  e.printStackTrace();              }          }        }  }
public class GetTarget implements Runnable {        private Vendor vendor;        public GetTarget(Vendor vendor) {          this.vendor = vendor;      }        @Override      public void run() {          while(true){              vendor.consumers();              try {                  Thread.sleep(1000);              } catch (InterruptedException e) {                  // TODO Auto-generated catch block                  e.printStackTrace();              }          }      }  }

​ 3,主方法。

public class Demo {      public static void main(String[] args) {          Vendor vendor = new Vendor();          SetTarget set = new SetTarget(vendor);          GetTarget get = new GetTarget(vendor);            // 開啟執行緒生產商品          new Thread(set).start();          new Thread(set).start();          new Thread(set).start();          new Thread(set).start();            // 開啟消費者執行緒          new Thread(get).start();      }  }

​ 4,運行結果。

五,總結

​ 執行緒之間通訊就做這麼一個簡單的總結,以上內容如有錯誤,歡迎留言指正。

感謝閱讀!