1. 程式人生 > >Java SE多執行緒部分--20.等待喚醒機制

Java SE多執行緒部分--20.等待喚醒機制

1、概述

等待喚醒機制就是用於解決執行緒間通訊的問題、常見的方法如下:

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

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

下面來說下生產者和消費者的問題 :

執行緒A用來生產了一產品,執行緒B用來消費了一個產品,產品可以理解為同一資源,執行緒A與執行緒B處理的動作,
一個是生產,一個是消費,那麼執行緒A與執行緒B之間就要用到等待喚醒機制。

2、例項

例子 :以包子為例

生產執行緒已經生產完畢包子了, 此時, 不再生產, 而是等待消費執行緒來消費. 如果生產執行緒沒有包子, 此時, 就需要生產, 生產完畢後, 喚醒消費執行緒來消費.

    消費執行緒消費完畢包子後, 不再消費, 此時, 消費執行緒就需要喚醒生產執行緒來生產. 如果消費執行緒沒有包子, 消費執行緒就進入到等待狀態.

 共享資源類 :

public class BaoZi {
    // 屬性
    private String pier;
    private String xianer;

    // 型別
    boolean type = false;
    boolean flag = false;

    public BaoZi(String pier, String xianer) {
        this.pier = pier;
        this.xianer = xianer;
    }

    public BaoZi() {
    }

    @Override
    public String toString() {
        return "BaoZi{" +
                "pier='" + pier + '\'' +
                ", xianer='" + xianer + '\'' +
                '}';
    }

    public String getPier() {
        return pier;
    }

    public void setPier(String pier) {
        this.pier = pier;
    }

    public String getXianer() {
        return xianer;
    }

    public void setXianer(String xianer) {
        this.xianer = xianer;
    }
}

生產執行緒 :

public class BaoZiPu extends Thread {
    // 屬性 (記錄資料)
    private BaoZi baoZi;

    // 構造方法
    public BaoZiPu(BaoZi baoZi, String name) {
        super(name);
        this.baoZi = baoZi;
    }

    // 重寫 Thread 類的 run 方法
    @Override
    public void run() {
        // 不斷生產
        while (true) {

            // 同步鎖環境
            synchronized (baoZi) {
                // 生產功能
                produce();
            }
        }
    }

    // 生產功能 : 子執行緒
    public void produce() {

        // 隨機時間切換 : 0 ~ 500 毫秒   (long)(Math.random() * 500)
        /*try {
            Thread.sleep((long)(Math.random() * 500));
        } catch (InterruptedException e) {
            e.printStackTrace();
        }*/

        // 1. 有包子, 生產執行緒進入等待
        if (baoZi.flag == true) {
            try {
                baoZi.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        // 2. 沒有包子, 生產執行緒進行生產
        // 2.1 判斷資料型別, 賦值不同的資料
        if (baoZi.type == false) {
            // 大蔥牛肉
            baoZi.setPier("大蔥");
            baoZi.setXianer("牛肉");
        } else {
            // 金牌蝦仁
            baoZi.setPier("金牌");
            baoZi.setXianer("蝦仁");
        }

        // 2.2 更改型別 (型別取反即可)
        baoZi.type = !baoZi.type;

        // 2.3 輸出語句
        System.out.println(Thread.currentThread().getName() + " 生產了" + baoZi.getPier() + baoZi.getXianer() + " 的包子.");

        // 2.4 更改包子的標記
        baoZi.flag = true;

        // 3. 喚醒消費執行緒
        baoZi.notify();
    }
}

消費執行緒 :

public class ChiHuo extends Thread  {
    // 屬性 (記錄資料)
    private BaoZi baoZi;

    // 構造方法
    public ChiHuo(BaoZi baoZi, String name) {
        super(name);
        this.baoZi = baoZi;
    }

    // 重寫 Thread 類的 run 方法
    @Override
    public void run() {
        // 不斷消費
        while (true) {
            synchronized (baoZi) {
                // 消費功能
                consume();
            }
        }
    }

    // 消費功能
    public void consume() {

        // 隨機時間切換 : 0 ~ 500 毫秒   (long)(Math.random() * 500)
        /*try {
            Thread.sleep((long)(Math.random() * 500));
        } catch (InterruptedException e) {
            e.printStackTrace();
        }*/

        // 1. 判斷, 沒有包子, 死等
        if (baoZi.flag == false) {
            try {
                baoZi.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        // 2.1 取出資料, 消費
        System.out.println(Thread.currentThread().getName() + " 正在吃 " + baoZi.getPier() + baoZi.getXianer() + " 的包子.");

        // 2.2 將資料清空
        baoZi.setPier("null");
        baoZi.setXianer("null");

        // 2.3 修改包子的標記
        baoZi.flag = false;

        // 3. 喚醒生產執行緒生產包子
        baoZi.notify();
    }
}

測試類 :

public class ProduceAndConsumeTest {
    public static void main(String[] args) {

        // 準備一個共享資料類
        BaoZi baoZi = new BaoZi();

        // 生產執行緒
        BaoZiPu baoZiPu = new BaoZiPu(baoZi, "生產執行緒");
        // 消費執行緒
        ChiHuo chiHuo = new ChiHuo(baoZi, "消費執行緒");

        // 啟動執行緒
        baoZiPu.start();
        chiHuo.start();
    }
}