對 Java 中等待喚醒機制的理解


線程的狀態

首先了解一下什么是線程的狀態,線程狀態就是當線程被創建(new),並且啟動(start)后,它不是一啟動就進入了執行狀態(run),也不是一直都處於執行狀態。

這里說一下Java 的Thread類里面有一個State方法,這個方法里面涵蓋了6種線程的狀態,如下:

public enum State {
    // 尚未啟動的線程的線程狀態。
    NEW,

    // 可運行線程的線程狀態。
    RUNNABLE,

    // 線程的線程狀態被阻塞,等待監視器鎖定。
    BLOCKED,

    // 等待線程的線程狀態。
    WAITING,

    // 具有指定等待時間的等待線程的線程狀態。
    TIMED_WAITING,

    // 終止線程的線程狀態。
    TERMINATED;
}

導致這六種線程狀態發生的條件

  1. New -- 新建
    線程剛被創建,不過還沒有被啟動(還沒有調用start方法)

  2. Runnable -- 可運行
    處於可運行狀態的線程正在Java虛擬機中執行,但是它可能正在等待來自操作系統(例如處理器)的其他資源。

  3. Blocked -- 鎖阻塞
    當一個線程想獲取一個對象鎖,不過該對象鎖被其它的線程持有時,該線程就會進入鎖阻塞狀態;當該線程持有鎖的時候,該線程將會變成可運行的狀態.

  4. Waiting -- 無限等待
    當一個線程在等待另一個線程執行一個(喚醒)動作時,該線程就會進入無限等待狀態。進入這個狀態后是不能自動喚醒的,要等待另一個線程調用notify()方法,或notifyall()方法才能夠被喚醒。

  5. Timed_Waiting -- 計時等待
    類似於無限等待狀態,有幾個方法有超時參數,如:Thread.sleep、Object.wait方法。調用這些方法,進入計時等待狀態。計時等待狀態將會一直保持到超時期滿或者接收到喚醒通知。

  6. terminated -- 被終止

    1. 因為run方法的正常退出而死亡。
    2. 因為沒有捕獲的異常,終止了run方法而死亡。

等待喚醒案例切入

  1. 顧客要去飯店吃飯,自助下單,說明要吃什么,數量是多少。下完單以后,顧客就等待該飯店廚師做飯菜,也就是Waiting狀態(無限等待狀態)。
  2. 廚師收到下單信息,開始做飯菜,做好飯菜,把飯菜遞到顧客桌面上,顧客看到飯菜已經來了(notify方法),就可以開吃了(等待喚醒機制)。

線程之間的通信

分析

創建一個顧客線程:下單,告知廚師要什么菜,菜的數量,調用wait方法,放棄CPU的執行,進入到無限等待狀態(Waiting)

創建一個廚師線程:看到下單,花了3秒鍾做飯菜,做好之后,調用notify方法,喚醒顧客吃飯了。

注意

顧客線程和廚師線程,必須使用同步代碼塊包裹起來,保證等待和喚醒只能有一個在執行。

同步使用的鎖對象必須保證唯一。

只有鎖對象才能夠調用Object.wait方法和Object.notify方法。

Java 代碼實現

public class Demo01WaitNotify {
    public static void main(String[] args) {
        // 創建鎖對象(要保證鎖唯一)
        Object object = new Object();

        // 創建一個顧客線程
        new Thread() {
            @Override
            public void run() {
                // 使用同步代碼塊包裹起來,保證等待和喚醒只能有一個在執行。
                synchronized (object) {
                    // 顧客下單
                    System.out.println("我要一個西虹市炒番茄,一個馬鈴薯炒土豆,兩碗米飯");
                    // 調用wait方法,放棄CPU的執行,進入到無限等待狀態(Waiting)
                    try {
                        object.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    // 喚醒之后(飯菜上來后),吃飯!!!真香。
                    System.out.println("我就是餓死,從這里跳下去,也不會吃你們一口飯。。。真香!!!!");
                }
            }
        }.start();

        // 創建一個廚師線程
        new Thread() {
            @Override
            public void run() {
                // 廚師收到下單請求,花三秒鍾把飯菜做好
                try {
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                // 使用同步代碼塊包裹起來,保證等待和喚醒只能有一個在執行。
                synchronized (object) {
                    System.out.println("我的飯菜三秒鍾做好了,你食唔食哦?");
                    // 做好之后,調用notify方法,喚醒顧客吃飯了。
                    object.notify();
                }
            }
        }.start();
    }
}
控制台輸出:
我要一個西虹市炒番茄,一個馬鈴薯炒土豆,兩碗米飯
我的飯菜三秒鍾做好了,你食唔食哦?
我就是餓死,從這里跳下去,也不會吃你們一口飯。。。真香!!!!

備注:上面的代碼,存在線程間的通信,那什么又是線程間的通信呢?簡單的說,就是多個線程在處理同一個資源,但是處理的動作(線程的任務)卻不同。如上,廚師線程做飯菜,顧客線程吃飯菜。那為什么要進行線程間的通信呢?多個線程並發執行的時候,在默認情況下CPU是隨機切換線程的,當我們需要多個線程來共同完成一件任務,並且希望它們有規律的執行的時候,那么多線程就之間就需要一些協調通信,來達到多線程共同操作一份數據。

對代碼中通信的理解

對又沒有飯菜進行判斷——

1、沒有飯菜(False)。

2、顧客下單。

3、廚師做飯菜。

4、顧客線程等待。

5、廚師做好飯菜

6、修改飯菜的狀態(True)

7、有飯菜,廚師線程提醒顧客線程吃飯菜。

8、廚師線程等待

9、吃完飯菜,修改飯菜的狀態(False)

這就是顧客線程與廚師線程之間的通信。以此類推,其它Java程序中多線程的通信也是同樣的道理。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM