Java 多線程學習(5)畫一張漂亮的圖幫助理解 Java 線程的狀態轉換

最開始的打算是隻寫線程的狀態及轉換的(即下文中的 一、2 點),由於在看了 Thread 源碼和別人寫的關於線程狀態轉換的博客以後,內心挺明瞭的,以爲本身應該理解了。。。html

不過在學 AQS 的時候發現用到了不少 LockSupport.park() 和 LockSupport.unpark(),涉及到掛起線程與喚醒線程,這個時候才意識到本身對於這一塊其實並非太熟,並不太清楚 LockSupport.park() 和 LockSupport.unpark() 是怎麼用的(只知道一個會掛起線程,另外一個會喚醒線程,可是其中的原理並不清楚),因而又跑去看 LockSupport 的源碼、學習 Object.wait() 和 Object.notify() 的使用及原理,此時才以爲對線程狀態的轉換纔算是入門了。java

切實體會到了紙上談兵是多麼的好笑!好了,進入正題。git


線程都有哪些狀態呢?它們之間是如何轉換的呢?如何經過代碼來完成這些轉換呢?這就是這篇文章要解決的問題。github

其實以前對線程的狀態仍是瞭解過的,不過沒有系統的整理過,有些知識點仍是略微模糊的。例如:oracle

  • 在哪些狀況下線程會進入等待狀態?(又分爲限期等待和無限期等待兩種)
  • 阻塞狀態和等待狀態的區別?
  • Object.wait() 和 Object.notify()、LockSupport.park() 和 LockSupport.unpark() 如何使用?
  • ...

一、線程的 6 種狀態

Java 線程(爲何說是 Java 線程呢?由於操做系統線程狀態和這 6 個狀態還有些許差別)一共有 6 種狀態,Thread 類中專門定義了一個枚舉類 State 來表示這 6 種狀態。學習

(1)NEW(新建)

建立了一個 Thread 實例,尚未調用其 start() 方法,此時線程處於新建狀態。ui

(2)RUNNABLE(運行)

運行狀態包含了 Ready 和 Running 兩種狀態。什麼意思呢?spa

Ready 狀態的意思就是我已經準備好一切,只要讓我用 CPU 我就能 Running,即正在等待 CPU 分配執行時間;操作系統

Running 狀態就是獲得了 CPU 的執行時間,正在運行。.net

(3)BLOCKED(阻塞)

線程 A 進入同步區域的時候,須要獲取一個排他鎖,若此時排他鎖正在被另外一個線程 B 佔有,線程 A 將進入阻塞狀態。

(4)WAITING(無限期等待)

處於這種狀態的線程不會被分配 CPU 執行時間,它們要等待被其餘線程顯式地喚醒。如下方法會讓線程陷入無限期的等待狀態:

  • 沒有設置 Timeout 參數的 Object.wait() 方法;
  • 沒有設置 Timeout 參數的 Thread.join() 方法;
  • LockSupport.park() 方法。

(5)TIMED_WAITING(限期等待)

處於這種狀態的線程也不會被分配 CPU 執行時間,不過無須等待被其餘線程顯式地喚醒,在必定時間以後它們會由系統自動喚醒。如下方法會讓線程進入限期等待狀態:

  • Thread.sleep() 方法;
  • 設置了 Timeout 參數的 Object.wait() 方法;
  • 設置了 Timeout 參數的 Thread.join() 方法;
  • LockSupport.parkNanos() 方法;
  • LockSupport.parkUntil() 方法。

(6)TERMINATED(結束)

run() 方法執行完成,線程結束執行,此時的線程狀態。

二、線程的狀態轉換圖

上一節講了線程的 6 個狀態以及線程進入這 6 個狀態的場景,其實不是很直觀;下面的這幅圖很直觀的展現了線程狀態的轉換(建議本身去畫一遍這個圖,若是能不借助外物就能畫出來的話,基本上線程狀態轉換也就沒什麼問題了)。

線程的狀態轉換

我我的以爲這圖還挺好看的!不知諸位以爲如何?

三、如何經過代碼展現上述幾個狀態?

(1)NEW(新建)

private static void newState() {
        Thread newThread = new Thread("00-newThread");
        System.out.println(newThread.getState());
    }
複製代碼

線程-新建-1

線程-新建-2

(2)RUNNABLE(運行)

private static void runnableState() {
        Thread runnableThread = new Thread(() -> {
            while (true) {

            }
        }, "01-runnableThread");

        runnableThread.start();

        System.out.println(runnableThread.getState());
    }
複製代碼

線程-運行

(3)BLOCKED(阻塞)

private static void blockedState() {

        Thread holdLockThread = new Thread(() -> {
            synchronized (lock) {
                while (true) {

                }
            }
        }, "holdLockThread");

        holdLockThread.start();

        Thread blockedThread = new Thread(() -> {
            synchronized (lock) {
                while (true) {

                }
            }
        }, "04-blockedThread");

        blockedThread.start();

        System.out.println(blockedThread.getState());
    }
複製代碼

線程-阻塞

(4)WAITING(無限期等待)

private static void waitingState() {
        Thread waitingThread = new Thread(() -> {
            LockSupport.park();
        }, "02-waitingThread");

        waitingThread.start();

        System.out.println(waitingThread.getState());
    }
複製代碼

線程-無限期等待

(5)TIMED_WAITING(限期等待)

private static void timeWaitingState() {
        Thread timeWaitingThread = new Thread(() -> {
            LockSupport.parkUntil(LocalDateTime.now().plusDays(1).toEpochSecond(ZoneOffset.of("+8")) * 1000);
        }, "03-timeWaitingThread");

        timeWaitingThread.start();

        System.out.println(timeWaitingThread.getState());
    }
複製代碼

線程-限期等待

(6)TERMINATED(結束)

private static void terminatedState() {
        Thread terminatedThread = new Thread("05-terminatedThread");
        terminatedThread.start();
        try {
            terminatedThread.join();// 等待 terminatedThread 執行完成
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(terminatedThread.getState());
    }
複製代碼

線程-結束

四、Object.wait() 和 Object.notify()

private static void waitAndNotify() {

        Thread waitThread = new Thread(() -> {
            synchronized (waitAndNotify) {
                System.out.println("I'm wait thread.");
                try {
                    System.out.println("waiting...");
                    waitAndNotify.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("notified.");
            }
        }, "waitThread");

        Thread notifyThread = new Thread(() -> {
            synchronized (waitAndNotify) {
                System.out.println("I'm notified thread.");
                waitAndNotify.notify();
                System.out.println("notify wait thread.");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("after sleep 1 second.");
            }
        }, "notifyThread");

        waitThread.start();
        try {
            Thread.sleep(20);// 確保 waitThread 在 notifyThread 以前執行
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        notifyThread.start();
    }
複製代碼

輸出:

I'm wait thread.
waiting...
I'm notified thread.
notify wait thread.
after sleep 1 second.
notified.
複製代碼

關於 Object.wait() 和 Object.notify() 補充幾點:

(1)當前線程必須是 waitAndNotify monitor 的持有者,若是不是會拋 IllegalMonitorStateException;

(2)調用 waitAndNotify.wait() 方法會將當前線程放入 waitAndNotify 對象的 wait set 中等待被喚醒;而且釋放其持有的全部鎖;

(3)調用 waitAndNotify.notify() 方法會從 waitAndNotify 對象的 wait set 中喚醒一個線程(此例中的 waitThread),不過 waitThread 不會立刻執行,它必須等待 notifyThread 釋放 waitAndNotify 鎖;當 waitThread 再次得到 waitAndNotify 鎖,才能夠再次執行。也就解釋了爲何 notified. 會在最後輸出。

五、LockSupport.park() 和 LockSupport.unpark()

private static void parkAndUnpark() {

        Thread parkThread = new Thread(() -> {
            System.out.println("park.");
            LockSupport.park();
            System.out.println("unpark.");
        }, "parkThread");

        parkThread.start();

        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("after sleep 1 second");

        LockSupport.unpark(parkThread);
    }
複製代碼

輸出:

park.
after sleep 1 second
unpark.
複製代碼

六、結語

本文差很少結束了,完整代碼可在這裏找到。不過遺留的問題其實還有不少:

  • 線程的中斷?
  • Thread 中的 native 方法的底層實現(C++源碼)
  • Object.wait() 和 Object.notify() 的底層原理(C++源碼)
  • LockSupport.park() 和 LockSupport.unpark() 的底層原理(C++源碼)

遺留的這些問題基本上都是一些 native 方法,須要跟蹤 C++ 源碼;或者查看官方文檔,好比: Threads and Locks

通過查閱資料以及對 C++ 源碼的摸索,對於上面幾個問題背後的原理我也正在理解中,後續我會將本身的一些理解整理出來。

我一直堅信,鑽研技術要作到:知其然,知其因此然!

參考資料:

(1)Thread.java 源碼

(2)《深刻理解 Java 虛擬機》第二版.

相關文章
相關標籤/搜索