10 張圖聊聊執行緒的生命週期和常用 APIs

小齊本齊發表於2020-11-23

上一篇文章我們聊了多執行緒的基礎內容,比如為什麼要使用多執行緒,執行緒和程式之間的不同,以及建立執行緒的 4 種方式。

今天我們來說一下執行緒的生命週期和常用 APIs:我們需要非常清楚的知道執行緒的各種狀態,比如排查程式執行慢的原因時,就需要看下是不是哪裡被阻塞了;另外它也是面試時非常喜歡問的,如果基礎內容都答不好,恐怕直接就掛了。

本文已收錄至我的 Github: https://github.com/xiaoqi6666/NYCSDE

本文分為兩大部分,

  1. 執行緒的 6 大狀態;
  2. 多執行緒常用的 APIs:
    1. join()
    2. wait()
    3. notify()
    4. yield()
    5. sleep()
    6. currentThread()
    7. getName()
    8. getId()
    9. getPriority()
    10. setPriority()
    11. stop()

執行緒狀態

關於執行緒的狀態,網上各種說法都有,比較流行的是 5 種或者 6 種。關於 5 種狀態的那個版本我沒有找到理論依據,如果有小夥伴清楚的也歡迎留言指出。

我這裡所寫的是根據 java.lang.Thread 的原始碼,執行緒有以下 6 大狀態:

public enum State {
  NEW,
  RUNNABLE,
  BLOCKED,
  WAITTING,
  TIMED_WAITTING,
  TERMINATED;
}

先上圖,我們再依次來看。

1. New

A thread that has not yet started is in this state.

就是指執行緒剛建立,還沒啟動的時候,比如剛 new 了一個 thread

MyThread myThread = new MyThread();

2. Runnable

A thread is executing in the Java virtual machine but it may be waiting for other resources from the operating system such as processor.

那麼接下來,自然就是要啟動執行緒了,也就是呼叫 threadstart() 方法。

myThread.start();

啟動之後,執行緒就進入了 Runnable 狀態。

此時所有的執行緒都會新增到一個等待佇列裡,等待“CPU 排程”。

如果搶佔到 CPU 的資源,那就執行;如果沒搶到,就等著唄,等當前正在執行的執行緒完成它能執行的時間片之後,再次搶佔。

要注意這裡在等待的一般是系統資源,而不是鎖或者其他阻塞。

3. Blocked

Thread state for a thread blocked waiting for a monitor lock.
A thread in the blocked state is waiting for a monitor lock to enter a synchronized block/method or reenter a synchronized block/method after calling wait() Object.

這裡給出了非常明確的 use case,就是被鎖在外面的才叫阻塞。所以這裡必須要有至少 2 個執行緒。

4. Waiting

A thread in the waiting state is waiting for another thread to perform a particular action.

那具體有哪些原因呢?

A thread is in the waiting state due to calling one of the following methods:

  • Object.wait with no timeout
  • Thread.join with no timeout
  • LockSupport.park

所以說,當呼叫了

  • wait()
  • join()
  • park()
    方法之後,執行緒進入等待狀態。

這裡的等待狀態是沒有時間限制的,可以無限的等下去... 所以需要有人來喚醒

  1. 如果是通過 wait() 進入等待狀態的,需要有 notify() 或者 notifyAll() 方法來喚醒;
  2. 如果是通過 join() 進入等待狀態的,需要等待目標執行緒執行結束。

比如在生產者消費者模型裡,當沒有商品的時候,消費者就需要等待,等待生產者生產好了商品發 notify()。下一篇文章我們會細講。

5. Timed_waiting

導致這個狀態的原因如下:

  • Thread.sleep
  • Object.wait with timeout
  • Thread.join with timeout
  • LockSupport.parkNanos
  • LockSupport.parkUntil

其實就是在上一種狀態的基礎上,給了具體的時間限制

那麼當時間結束後,執行緒就解放了。

6. Terminated

A thread that has exited is in this state.

這裡有 3 種情況會終止執行緒:

  • 執行完所有程式碼,正常結束;
  • 強制被結束,比如呼叫了 stop() 方法,現在已經被棄用;
  • 丟擲了未捕獲的異常。

執行緒一旦死亡就不能復生。

如果在一個死去的執行緒上呼叫 start() 方法,那麼程式會丟擲 java.lang.IllegalThreadStateException

接下來我們說說多執行緒中常用的 11 個 APIs。

APIs

1. join()

join() 方法會強制讓該執行緒執行,並且一直會讓它執行完。

比如上一篇文章的例子是兩個執行緒交替執行的,那麼我們這裡該下,改成呼叫小齊執行緒.join(),那麼效果就是先輸出 小齊666

public class MyRunnable implements Runnable {
    @Override
    public void run() {
        for(int i = 0; i < 100; i++) {
            System.out.println("小齊666:" + i);
        }
    }

    public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread(new MyRunnable());
        t.start();
        t.join();

        for(int i = 0; i < 100; i++) {
            System.out.println("主執行緒" + i + ":齊姐666");
        }
    }
}
圖2
圖2

所以 join() 能夠保證某個執行緒優先執行,而且會一直讓它執行完,再回歸到公平競爭狀態。

join() 方法其實是用 wait() 來實現的,我們來看下這個方法。

2. wait() and notify()

wait() 其實並不是 Thread 類的方法,而是 Object 裡面的方法。

該方法就是讓當前物件等待,直到另一個物件呼叫 notify() 或者 notifyAll()

當然了,我們也可以設定一個等待時長,到時間之後物件將會自動甦醒。

4. yield()

yield 本身的中文意思是屈服,用在這裡倒也合適。

yield() 表示當前執行緒主動讓出 CPU 資源一下,然後我們再一起去搶。

注意這裡讓一下真的只是一下,從“執行中”回到“等待 CPU 分配資源”,然後所有執行緒再一起搶佔資源。

5. sleep()

顧名思義,這個方法就是讓當前執行緒睡一會,比如說,

myThread.sleep(1000); // 睡眠 1 秒鐘

它會丟擲一個 InterruptedException 異常,所以還要 try catch 一下。

6. currentThread()

Returns a reference to the currently executing thread object.

該方法是獲取當前執行緒物件。

注意它是一個 static 方法,所以直接通過 Thread 類呼叫。

比如列印當前執行緒

System.out.println(Thread.currentThread());

前文的例子中,它會輸出:

Thread[Thread-0,5,main]
Thread[main,5,main]

沒錯,它的返回值也是 Thread 型別。

7. getName()

該方法可以獲取當前執行緒名稱。

這個名稱可以自己設定,比如:

Thread t = new Thread(new MyRunnable(), "壹齊學");

8. getId()

該方法是獲取執行緒的 Id.

9. getPriority()

執行緒也有優先順序的哦~

雖然優先順序高的執行緒並不能百分百保證一定會先執行,但它是有更大的概率被先執行的。

優先順序的範圍是 1-10,我們來看原始碼:

    /**
     * The minimum priority that a thread can have.
     */

    public final static int MIN_PRIORITY = 1;

   /**
     * The default priority that is assigned to a thread.
     */

    public final static int NORM_PRIORITY = 5;

    /**
     * The maximum priority that a thread can have.
     */

    public final static int MAX_PRIORITY = 10;

如果不在這個範圍,JDK 丟擲 IllegalArgumentException() 的異常。

10. setPriority()

當然啦,我們也是可以自己設定某個執行緒的優先順序的。

設定的優先順序也需要在規定的 1-10 的範圍內哦,如果不在這個範圍也會拋異常。

11. stop()

最後我們來說下 stop() 方法,也是前文提到過的強制停止執行緒的一種方式,但現在已被棄用,因為會引起一些執行緒安全方面的問題。

好了,以上就是有關執行緒狀態和常用 API 的介紹了。相信大家看完之後對執行緒的整個流程應該有了清晰的認識,其實裡面還有很多細節我沒有展開,畢竟這是多執行緒的第 2 講,更深入的內容我們慢慢來。

如果你喜歡這篇文章,記得給我點贊留言哦~你們的支援和認可,就是我創作的最大動力,我們下篇文章見!

我是小齊,紐約程式媛,終生學習者,每天晚上 9 點,雲自習室裡不見不散!

更多幹貨文章見我的 Github: https://github.com/xiaoqi6666/NYCSDE

相關文章