1. 程式人生 > 程式設計 >Java等待喚醒機制執行緒通訊原理解析

Java等待喚醒機制執行緒通訊原理解析

這篇文章主要介紹了Java等待喚醒機制執行緒通訊原理解析,文中通過示例程式碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友可以參考下

執行緒間通訊

概念:多個執行緒在處理同一個資源,但是處理的動作(執行緒的任務)卻不相同。比如:執行緒A用來生成包子的,執行緒B用來吃包子的,包子可以理解為同一資源,執行緒A與執行緒B處理的動作,一個是生產,一個是消費,那麼執行緒A與執行緒B之間就存線上程通訊問題。

為什麼要處理執行緒間通訊:

多個執行緒併發執行時,在預設情況下CPU是隨機切換執行緒的,當我們需要多個執行緒來共同完成一件任務,並且我們希望他們有規律的執行,那麼多執行緒之間需要一些協調通訊,以此來幫我們達到多執行緒共同操作一份資料。

如何保證執行緒間通訊有效利用資源:

多個執行緒在處理同一個資源,並且任務不同時,需要執行緒通訊來幫助解決執行緒之間對同一個變數的使用或操作。 就是多個執行緒在操作同一份資料時, 避免對同一共享變數的爭奪。也就是我們需要通過一定的手段使各個執行緒能有效的利用資源。而這種手段即—— 等待喚醒機制。

等待喚醒機制

什麼是等待喚醒機制

這是多個執行緒間的一種協作機制。談到執行緒我們經常想到的是執行緒間的競爭(race),比如去爭奪鎖,但這並不是故事的全部,執行緒間也會有協作機制。就好比在公司裡你和你的同事們,你們可能存在在晉升時的競爭,但更多時候你們更多是一起合作以完成某些任務。就是在一個執行緒進行了規定操作後,就進入等待狀態(wait()), 等待其他執行緒執行完他們的指定程式碼過後 再將其喚醒(notify());在有多個執行緒進行等待時, 如果需要,可以使用 notifyAll()來喚醒所有的等待執行緒。wait/notify 就是執行緒間的一種協作機制。

等待喚醒中的方法

等待喚醒機制就是用於解決執行緒間通訊的問題的,使用到的3個方法的含義如下:

  • wait:執行緒不再活動,不再參與排程,進入 wait set 中,因此不會浪費 CPU 資源,也不會去競爭鎖了,這時的執行緒狀態即是 WAITING。它還要等著別的執行緒執行一個特別的動作,也即是“通知(notify)”在這個物件上等待的執行緒從wait set 中釋放出來,重新進入到排程佇列(ready queue)中
  • notify:則選取所通知物件的 wait set 中的一個執行緒釋放;例如,餐館有空位置後,等候就餐最久的顧客最先入座。
  • notifyAll:則釋放所通知物件的 wait set 上的全部執行緒。

注意:

哪怕只通知了一個等待的執行緒,被通知執行緒也不能立即恢復執行,因為它當初中斷的地方是在同步塊內,而此刻它已經不持有鎖,所以她需要再次嘗試去獲取鎖(很可能面臨其它執行緒的競爭),成功後才能在當初呼叫 wait 方法之後的地方恢復執行。

總結如下:

如果能獲取鎖,執行緒就從 WAITING 狀態變成 RUNNABLE 狀態;否則,從 wait set 出來,又進入 entry set,執行緒就從 WAITING 狀態又變成 BLOCKED 狀態呼叫wait和notify方法需要注意的細節

  • wait方法與notify方法必須要由同一個鎖物件呼叫。因為:對應的鎖物件可以通過notify喚醒使用同一個鎖物件呼叫的wait方法後的執行緒。
  • wait方法與notify方法是屬於Object類的方法的。因為:鎖物件可以是任意物件,而任意物件的所屬類都是繼承了Object類的。
  • wait方法與notify方法必須要在同步程式碼塊或者是同步函式中使用。因為:必須要通過鎖物件呼叫這2個方法。

生產者與消費者問題

等待喚醒機制其實就是經典的“生產者與消費者”的問題。就拿生產包子消費包子來說等待喚醒機制如何有效利用資源:

/*
包子鋪執行緒生產包子,吃貨執行緒消費包子。當包子沒有時(包子狀態為false),吃貨執行緒等待,包子鋪執行緒生產包子
(即包子狀態為true),並通知吃貨執行緒(解除吃貨的等待狀態),因為已經有包子了,那麼包子鋪執行緒進入等待狀態。
接下來,吃貨執行緒能否進一步執行則取決於鎖的獲取情況。如果吃貨獲取到鎖,那麼就執行吃包子動作,包子吃完(包
子狀態為false),並通知包子鋪執行緒(解除包子鋪的等待狀態),吃貨執行緒進入等待。包子鋪執行緒能否進一步執行則取
決於鎖的獲取情況

*/

程式碼實現

包子類

package demo01;

public class BaoZi {
  String pier;
  String xianer;
  boolean flag = false;//包子資源 是否存在 包子資源狀態
}

吃貨執行緒類:

package demo01;

public class ChiHuo extends Thread {
  private BaoZi bz;

  public ChiHuo(String name,BaoZi bz) {
    super(name);
    this.bz = bz;
  }

  @Override
  public void run() {
    while (true) {
      synchronized (bz) {
        if (bz.flag == false) {//沒包子
          try {
            bz.wait();
          } catch (InterruptedException e) {
            e.printStackTrace();
          }
        }
        System.out.println("吃貨正在吃" + bz.pier + bz.xianer + "包子");
        bz.flag = false;
        bz.notify();
      }
    }
  }
}

包子鋪執行緒類:

package demo01;

public class BaoZiPu extends Thread {
  private BaoZi bz;

  public BaoZiPu(String name,BaoZi bz) {
    super(name);
    this.bz = bz;
  }

  @Override
  public void run() {
    int count = 0;
    //造包子
    while (true) {
      //同步
      synchronized (bz) {
        if (bz.flag == true) {//包子資源 存在
          try {
            bz.wait();
          } catch (InterruptedException e) {
            e.printStackTrace();
          }
        }
        // 沒有包子 造包子
        System.out.println("包子鋪開始做包子");
        if (count % 2 == 0) {
          // 冰皮 五仁
          bz.pier = "冰皮";
          bz.xianer = "五仁";
        } else {
          // 薄皮 牛肉大蔥
          bz.pier = "薄皮";
          bz.xianer = "牛肉大蔥";
        }
        count++;
        bz.flag = true;
        System.out.println("包子造好了:" + bz.pier + bz.xianer);
        System.out.println("吃貨來吃吧");
        //喚醒等待執行緒 (吃貨)
        bz.notify();
      }
    }
  }
}

測試類:

package demo01;

public class Demo {
  public static void main(String[] args) {
    //等待喚醒案例
    BaoZi bz = new BaoZi();
    ChiHuo ch = new ChiHuo("吃貨",bz);
    BaoZiPu bzp = new BaoZiPu("包子鋪",bz);
    ch.start();
    bzp.start();
  }
}

以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支援我們。