死磕 java執行緒系列之執行緒的生命週期

彤哥讀原始碼發表於2019-10-18

thread_life

(手機橫屏看原始碼更方便)


注:java原始碼分析部分如無特殊說明均基於 java8 版本。

簡介

大家都知道執行緒是有生命週期,但是彤哥可以認真負責地告訴你網上幾乎沒有一篇文章講得是完全正確的。

常見的錯誤有:就緒狀態、執行中狀態(RUNNING)、死亡狀態、中斷狀態、只有阻塞沒有等待狀態、流程圖亂畫等,最常見的錯誤就是說執行緒只有5種狀態。

今天這篇文章會徹底講清楚執行緒的生命週期,並分析synchronized鎖、基於AQS的鎖中執行緒狀態變化的邏輯。

所以,對synchronized鎖和AQS原理(原始碼)不瞭解的同學,請翻一下彤哥之前的文章先熟悉這兩部分的內容,否則肯定記不住這裡講的執行緒生命週期。

問題

(1)執行緒的狀態有哪些?

(2)synchronized鎖各階段執行緒處於什麼狀態?

(3)重入鎖、條件鎖各階段執行緒處於什麼狀態?

先上原始碼

關於執行緒的生命週期,我們可以看一下java.lang.Thread.State這個類,它是執行緒的內部列舉類,定義了執行緒的各種狀態,並且註釋也很清晰。


public enum State {
    /**
     * 新建狀態,執行緒還未開始
     */
    NEW,

    /**
     * 可執行狀態,正在執行或者在等待系統資源,比如CPU
     */
    RUNNABLE,

    /**
     * 阻塞狀態,在等待一個監視器鎖(也就是我們常說的synchronized)
     * 或者在呼叫了Object.wait()方法且被notify()之後也會進入BLOCKED狀態
     */
    BLOCKED,

    /**
     * 等待狀態,在呼叫了以下方法後進入此狀態
     * 1. Object.wait()無超時的方法後且未被notify()前,如果被notify()了會進入BLOCKED狀態
     * 2. Thread.join()無超時的方法後
     * 3. LockSupport.park()無超時的方法後
     */
    WAITING,

    /**
     * 超時等待狀態,在呼叫了以下方法後會進入超時等待狀態
     * 1. Thread.sleep()方法後【本文由公從號“彤哥讀原始碼”原創】
     * 2. Object.wait(timeout)方法後且未到超時時間前,如果達到超時了或被notify()了會進入BLOCKED狀態
     * 3. Thread.join(timeout)方法後
     * 4. LockSupport.parkNanos(nanos)方法後
     * 5. LockSupport.parkUntil(deadline)方法後
     */
    TIMED_WAITING,

    /**
     * 終止狀態,執行緒已經執行完畢
     */
    TERMINATED;
}
複製程式碼

流程圖

執行緒生命週期中各狀態的註釋完畢了,下面我們再來看看各狀態之間的流轉:

thread_life

怎麼樣?是不是很複雜?彤哥幾乎把網上的資料都查了一遍,沒有一篇文章把這個流程圖完整畫出來的,下面彤哥就來一一解釋:

(1)為了方便講解,我們把鎖分成兩大類,一類是synchronized鎖,一類是基於AQS的鎖(我們拿重入鎖舉例),也就是內部使用了LockSupport.park()/parkNanos()/parkUntil()幾個方法的鎖;

(2)不管是synchronized鎖還是基於AQS的鎖,內部都是分成兩個佇列,一個是同步佇列(AQS的佇列),一個是等待佇列(Condition的佇列);

(3)對於內部呼叫了object.wait()/wait(timeout)或者condition.await()/await(timeout)方法,執行緒都是先進入等待佇列,被notify()/signal()或者超時後,才會進入同步佇列;

(4)明確宣告,BLOCKED狀態只有執行緒處於synchronized的同步佇列的時候才會有這個狀態,其它任何情況都跟這個狀態無關;

(5)對於synchronized,執行緒執行synchronized的時候,如果立即獲得了鎖(沒有進入同步佇列),執行緒處於RUNNABLE狀態;

(6)對於synchronized,執行緒執行synchronized的時候,如果無法獲得鎖(直接進入同步佇列),執行緒處於BLOCKED狀態;

(5)對於synchronized內部,呼叫了object.wait()之後執行緒處於WAITING狀態(進入等待佇列);

(6)對於synchronized內部,呼叫了object.wait(timeout)之後執行緒處於TIMED_WAITING狀態(進入等待佇列);

(7)對於synchronized內部,呼叫了object.wait()之後且被notify()了,如果執行緒立即獲得了鎖(也就是沒有進入同步佇列),執行緒處於RUNNABLE狀態;

(8)對於synchronized內部,呼叫了object.wait(timeout)之後且被notify()了,如果執行緒立即獲得了鎖(也就是沒有進入同步佇列),執行緒處於RUNNABLE狀態;

(9)對於synchronized內部,呼叫了object.wait(timeout)之後且超時了,這時如果執行緒正好立即獲得了鎖(也就是沒有進入同步佇列),執行緒處於RUNNABLE狀態;

(10)對於synchronized內部,呼叫了object.wait()之後且被notify()了,如果執行緒無法獲得鎖(也就是進入了同步佇列),執行緒處於BLOCKED狀態;

(11)對於synchronized內部,呼叫了object.wait(timeout)之後且被notify()了或者超時了,如果執行緒無法獲得鎖(也就是進入了同步佇列),執行緒處於BLOCKED狀態;

(12)對於重入鎖,執行緒執行lock.lock()的時候,如果立即獲得了鎖(沒有進入同步佇列),執行緒處於RUNNABLE狀態;

(13)對於重入鎖,執行緒執行lock.lock()的時候,如果無法獲得鎖(直接進入同步佇列),執行緒處於WAITING狀態;

(14)對於重入鎖內部,呼叫了condition.await()之後執行緒處於WAITING狀態(進入等待佇列);

(15)對於重入鎖內部,呼叫了condition.await(timeout)之後執行緒處於TIMED_WAITING狀態(進入等待佇列);

(16)對於重入鎖內部,呼叫了condition.await()之後且被signal()了,如果執行緒立即獲得了鎖(也就是沒有進入同步佇列),執行緒處於RUNNABLE狀態;

(17)對於重入鎖內部,呼叫了condition.await(timeout)之後且被signal()了,如果執行緒立即獲得了鎖(也就是沒有進入同步佇列),執行緒處於RUNNABLE狀態;

(18)對於重入鎖內部,呼叫了condition.await(timeout)之後且超時了,這時如果執行緒正好立即獲得了鎖(也就是沒有進入同步佇列),執行緒處於RUNNABLE狀態;

(19)對於重入鎖內部,呼叫了condition.await()之後且被signal()了,如果執行緒無法獲得鎖(也就是進入了同步佇列),執行緒處於WAITING狀態;

(20)對於重入鎖內部,呼叫了condition.await(timeout)之後且被signal()了或者超時了,如果執行緒無法獲得鎖(也就是進入了同步佇列),執行緒處於WAITING狀態;

(21)對於重入鎖,如果內部呼叫了condition.await()之後且被signal()之後依然無法獲取鎖的,其實經歷了兩次WAITING狀態的切換,一次是在等待佇列,一次是在同步佇列;

(22)對於重入鎖,如果內部呼叫了condition.await(timeout)之後且被signal()或超時了的,狀態會有一個從TIMED_WAITING切換到WAITING的過程,也就是從等待佇列進入到同步佇列;

為了便於理解,彤哥這裡每一條都分的比較細,麻煩耐心看完。

測試用例

看完上面的部分,你肯定想知道怎麼去驗證,下面彤哥就說說驗證的方法,先給出測試用例。

public class ThreadLifeTest {
    public static void main(String[] args) throws IOException {
        Object object = new Object();
        ReentrantLock lock = new ReentrantLock();
        Condition condition = lock.newCondition();

        new Thread(()->{
            synchronized (object) {
                try {
                    System.out.println("thread1 waiting");
                    object.wait();
//                    object.wait(5000);
                    System.out.println("thread1 after waiting");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "Thread1").start();

        new Thread(()->{
            synchronized (object) {
                try {
                    System.out.println("thread2 notify");
                    // 開啟或關閉這段註釋,觀察Thread1的狀態
//                    object.notify();【本文由公從號“彤哥讀原始碼”原創】
                    // notify之後當前執行緒並不會釋放鎖,只是被notify的執行緒從等待佇列進入同步佇列
                    // sleep也不會釋放鎖
                    Thread.sleep(10000000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "Thread2").start();

        new Thread(()->{
            lock.lock();
            System.out.println("thread3 waiting");
            try {
                condition.await();
//                condition.await(200, (TimeUnit).SECONDS);
                System.out.println("thread3 after waiting");
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }
        }, "Thread3").start();

        new Thread(()->{
            lock.lock();
            System.out.println("thread4");
            // 開啟或關閉這段註釋,觀察Thread3的狀態
//            condition.signal();【本文由公從號“彤哥讀原始碼”原創】
            // signal之後當前執行緒並不會釋放鎖,只是被signal的執行緒從等待佇列進入同步佇列
            // sleep也不會釋放鎖
            try {
                Thread.sleep(1000000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }
        }, "Thread4").start();

    }
}
複製程式碼

開啟或關閉上面註釋部分的程式碼,使用IDEA的RUN模式執行程式碼,然後點選左邊的一個攝像頭按鈕(jstack),檢視各執行緒的狀態。

注:不要使用DEBUG模式,DEBUG模式全都變成WAITING狀態了,很神奇。

thread_life

彩蛋

其實,本來這篇是準備寫執行緒池的生命週期的,奈何執行緒的生命週期寫了太多,等下一篇我們再來一起學習執行緒池的生命週期吧。


歡迎關注我的公眾號“彤哥讀原始碼”,檢視更多原始碼系列文章, 與彤哥一起暢遊原始碼的海洋。

qrcode

相關文章