Java多線程之線程狀態的遷移詳解

一、六種狀態

java.lang.Thread 的狀態分為以下 6 種,它們以枚舉的形式,封裝在瞭Thread類內部:

NEW:表示線程剛剛創建出來,還未啟動

RUNNABLE:可運行狀態,該狀態的線程可以是ready或running,唯一的決定因素是線程調度器

BLOCKED:阻塞,線程正在等待一個monitor鎖以便進入一個同步代碼塊

WAITING:等待,一種掛起等待的狀態。一個線程處於waiting是為瞭等待其他線程執行某個特定的動作。

TIMED_WAITING:定時等待。

TERMINATED:終結,線程執行結束後的狀態。

二、狀態遷移圖

線程遷移圖網上有很多,這是我自己參考著繪制的一張。

線程遷移圖雖然是背瞭忘忘瞭背,反反復復很多遍,但是記憶這張圖其實並不困難。首先就是NEW和TERMINATED狀態,一個表示剛剛創建,一個表示任務結束。

最重要的是記住WAITING和BLOCKED這兩種狀態與RUNNABLE相互切換的條件。

BLOCKED狀態在Java doc中的描述是“等待一個monitor鎖”,monitor對象是與對象實例相關聯的一個鎖對象,這個鎖對象實際上就是 synchronized 的具體實現,一般稱之為重量級鎖,進入同步代碼塊的過程,實際上就是獲取到 monitor 對象的鎖的過程。如果鎖被其他線程占用,當前線程就變成瞭BLOCKED狀態,如果得到瞭鎖,就由BLOCKED切換到RUNNABLE狀態。

WAITING 是一種掛起狀態,處於 waiting 的線程表示它正在等待一個有緣人~ 這個有緣人需要執行特定的動作才能解救 waiting 中的線程。就像孫悟空在五指山下等瞭五百年,隻有玄奘摘下山頂的符咒才能夠破土而出。

導致 WAITING 的情況隻有三種:

wait()

join()

LockSupport.park()

wait() 方法是 Object 的成員方法,它可以令當前線程針對於某個對象掛起等待,並釋放獲得的鎖資源,隻有當其他線程調用這個對象的notify()或 notifyAll() 方法,才能夠喚醒等待中的線程。註意,notify()或 notifyAll() 不會釋放鎖資源。

join() 方法是線程的一個成員方法,“加入一個線程”,這好像合情合理,比如 t1線程在執行的過程中調用瞭 t2.join(),那麼好吧, t1就會由 RUNNABLE 變為 WAITING,因為他要等待 t2 執行完後才會繼續執行,說白瞭,就是方便程序員讓線程插隊用的:

LockSupport.park()更方便,它是一個靜態方法,可以讓線程在調用的位置直接WAITING,然後在其他線程中,獲取到WAITING中的線程對象,傳入LockSupport(thread) 直接恢復運行。

三、線程狀態模擬

準備三個線程 monitor 監視線程,主要實時監視 t1線程的狀態;

t1 線程模擬各種狀態,t2 輔助 t1 模擬各種狀態:

public class ThreadState {
    static Object lock = new Object();
    
    // 模擬 NEW、RUNNABLE、WAITING、TIMED_WAITING、BLOCKED、TERMINATED
    public static void main(String[] args) {
        ThreadState thisObj = new ThreadState();
        Thread t1 = new Thread(() -> {
            try {
                // 先獲取 t2 對象
                Thread t2 = getThreadByName("t2");
                // 先執行一套邏輯,推遲同步代碼塊的調用
                String str = "";
                for (int i = 0; i < 10000; i++) {
                    str += i;
                }
                // 調用同步代碼塊
                thisObj.doSync();
                // t2準備插隊
                t2.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }, "t1");
        System.out.println("t1 剛創建:" + t1.getState());
        
        Thread t2 = new Thread(() -> {
            try {
                // 直接獲取同步鎖
                thisObj.doSync();
                // 釋放鎖後在運行一段時間
                TimeUnit.SECONDS.sleep(30);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }, "t2");
        
        Thread monitor = new Thread(() -> {
            Thread.State t1State = null;
            while (true) {
                if (!t1.getState().equals(t1State)) {
                    t1State = t1.getState();
                    System.out.println("t1 此刻狀態:" + t1.getState());
                }
                if (t1State.equals(Thread.State.TERMINATED))
                    break;
            }
        }, "monitor");
        
        monitor.start();
        t2.start();
        t1.start();
    }
    
    public synchronized void doSync() {
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        String str = "";
        for (int i = 0; i < 100000; i++) {
            str += i;
        }
    }
    
    public static Thread getThreadByName(String name) {
        Optional<Thread> first = Thread.getAllStackTraces()
                .keySet()
                .stream()
                .filter(thread -> thread.getName().equals(name))
                .findFirst();
        return first.get();
    }
}

輸出:

t1 剛創建:NEW
t1 此刻狀態:RUNNABLE
t1 此刻狀態:BLOCKED
t1 此刻狀態:TIMED_WAITING
t1 此刻狀態:TIMED_WAITING
t1 此刻狀態:RUNNABLE
t1 此刻狀態:WAITING
t1 此刻狀態:BLOCKED
t1 此刻狀態:TERMINATED

總結

線程狀態遷移是非常重要的多線程基礎知識,在調試多線程問題的時候,能夠發揮很大的作用。

6 種狀態不僅要熟記,而且在什麼情況下會出現這些狀態也要清晰明瞭。

如果條件允許,可以試著通過不同的方法來模擬線程的六種狀態的切換,可以加深對線程生命周期的理解。

到此這篇關於Java多線程之線程狀態的遷移詳解的文章就介紹到這瞭,更多相關java線程狀態遷移內容請搜索WalkonNet以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持WalkonNet!

推薦閱讀: