Java并發(fā)編程:深入理解Java線程狀態(tài)
在本文中,我們將深入探討 Java 線程的六種狀態(tài)以及它們之間如何相互轉換。線程狀態(tài)的轉換就如同生物從出生、成長到最終死亡的過程,也有一個完整的生命周期。
操作系統(tǒng)中的線程狀態(tài)
首先,讓我們看看操作系統(tǒng)中線程的生命周期是如何流轉的。

在操作系統(tǒng)中,線程共有 5 種狀態(tài):
- 新建(NEW):線程已創(chuàng)建,但尚未開始執(zhí)行。
 - 就緒(READY):線程等待使用 CPU,在被調度程序調用后可進入運行狀態(tài)。
 - 運行(RUNNING):線程正在使用 CPU。
 - 等待(WAITING):線程因等待事件或其他資源(如 I/O)而被阻塞。
 - 終止(TERMINATED):線程已完成執(zhí)行。
 
Java 線程的 6 種狀態(tài)
Java 中線程狀態(tài)的定義與操作系統(tǒng)中的并不完全相同,查看 JDK 中的java.lang.Thread.State可以找到 Java 線程狀態(tài)的定義:
public enum State {
    NEW,
    RUNNABLE,
    BLOCKED,
    WAITING,
    TIMED_WAITING,
    TERMINATED;
}它們之間的流程關系如下圖所示:

接下來,我們將對 Java 線程的六種狀態(tài)進行深入分析。
NEW(新建)
處于NEW狀態(tài)的線程實際上還沒有啟動。也就是說,Thread 實例的start()方法還沒有被調用??闪鬓D狀態(tài):RUNNABLE
public class ThreadStateDemo {
    public static void main(String[] args) {
        Thread thread = new Thread(() -> {});
        System.out.println(thread.getState());
    }
}輸出:
NEWRUNNABLE(可運行)
Java 中的Runable狀態(tài)對應操作系統(tǒng)線程狀態(tài)中的兩種狀態(tài),分別是Running和Ready,也就是說,Java 中處于Runnable狀態(tài)的線程有可能正在執(zhí)行,也有可能沒有正在執(zhí)行比如正在等待被分配 CPU 資源。
所以,如果一個正在運行的線程是Runnable狀態(tài),當它運行到任務的一半時,執(zhí)行該線程的 CPU 被調度去做其他事情,導致該線程暫時不運行,它的狀態(tài)依然不變,還是Runnable,因為它有可能隨時被調度回來繼續(xù)執(zhí)行任務??闪鬓D狀態(tài):BLOCKED、WAITING、TIMED_WAITING、TERMINATED在 Java 中,線程通過調用Thread實例的start()方法進入RUNNABLE狀態(tài)。
關于start()方法,有兩個問題需要思考一下:
- 能否對同一個線程重復調用
start()方法? - 如果一個線程已經執(zhí)行完畢并處于
TERMINATED狀態(tài),是否可以再次調用該線程的start()方法? 
為了分析這兩個問題,我們先來看看start()方法的源碼:
public synchronized void start() {
    if (threadStatus!= 0)
        thrownew IllegalThreadStateException();
    group.add(this);
    boolean started = false;
    try {
        start0();
        started = true;
    } finally {
        try {
            if (!started) {
                group.threadStartFailed(this);
            }
        } catch (Throwable ignore) {
        }
    }
}我們可以看到,在start()方法內部,有一個threadStatus變量。如果它不等于 0,調用start()方法將直接拋出異常。
接下來,調用了一個start0()方法,但它是一個本地方法,無法知道方法內如何處理threadStatus。但沒關系,我們可以在調用start()方法后輸出當前狀態(tài),并嘗試再次調用start()方法:
public class ThreadStateDemo {
    public static void main(String[] args) {
        Thread thread = new Thread(() -> {});
        System.out.println(thread.getState());
        thread.start(); // 第一次調用
        System.out.println(thread.getState());
        thread.start(); // 第二次調用
    }
}輸出:
NEW
RUNNABLE
Exception in thread "main" java.lang.IllegalThreadStateException
    at java.lang.Thread.start(Thread.java:708)
    at thread.basic.ThreadStateDemo.main(ThreadStateDemo.java:11)可以看到,第一次調用start()方法是可以的,但第二次調用會報錯,java.lang.Thread.start(Thread.java:708)指的是狀態(tài)檢查失敗:

查看獲取當前線程狀態(tài)的源碼:
public State getState() {
    // 獲取當前線程狀態(tài)
    return sun.misc.VM.toThreadState(threadStatus);
}
public static State toThreadState(int var0) {
    if ((var0 & 4) != 0) {
        return State.RUNNABLE;
    } elseif ((var0 & 1024) != 0) {
        return State.BLOCKED;
    } elseif ((var0 & 16) != 0) {
        return State.WAITING;
    } elseif ((var0 & 32) != 0) {
        return State.TIMED_WAITING;
    } elseif ((var0 & 2) != 0) {
        return State.TERMINATED;
    } else {
        return (var0 & 1) == 0 ? State.NEW : State.RUNNABLE;
    }
}我們可以看到,只有State.NEW的狀態(tài)值被計算為 0。
因此,結合上面的源碼,我們可以得到兩個問題的答案都是不可行的。start()方法只能在NEW狀態(tài)下調用。
BLOCKED(阻塞)
處于BLOCKED狀態(tài)的線程正在等待鎖的釋放。可流轉狀態(tài):RUNNABLE我們用一個生活中的例子來說明BLOCKED狀態(tài):
假設你去銀行辦理業(yè)務。當你來到某個窗口時,發(fā)現(xiàn)前面已經有人了。這時,你必須等待前面的人離開窗口,才能辦理業(yè)務。
假設你是線程 B,前面的人是線程 A。此時,A 占有了鎖(銀行辦理業(yè)務的窗口),B 正在等待鎖的釋放,線程 B 此時就處于 BLOCKED 狀態(tài)。
代碼示例如下:
public class BlockCase {
    private synchronized void businessProcessing() {
        try {
            System.out.println("Thread[" + Thread.currentThread().getName() + "] performs business processing");
            Thread.sleep(2000L);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    public static void main(String[] args) {
        BlockCase blockCase = new BlockCase();
        Thread A = new Thread(blockCase::businessProcessing, "A");
        Thread B = new Thread(blockCase::businessProcessing, "B");
        A.start();
        B.start();
        System.out.println("Thread[" + A.getName() + "] state:" + A.getState());
        System.out.println("Thread[" + B.getName() + "] state:" + B.getState());
    }
}這里使用Thread.sleep()來模擬業(yè)務處理所需的時間。
輸出:
Thread[A] performs business processing
Thread[A] state:RUNNABLE
Thread[B] state:BLOCKED
Thread[B] performs business processing注意:如果多次執(zhí)行輸出結果可能不相同,這是因為兩個線程誰先被調度是隨機的
WAITING(等待)
等待狀態(tài)。處于等待狀態(tài)的線程需要其他線程喚醒才能轉換為RUNNABLE狀態(tài)。可流轉狀態(tài):RUNNABLE調用以下三種方法會使線程進入等待狀態(tài):
Object.wait():使當前線程進入等待狀態(tài),直到另一個線程喚醒它;Thread.join():等待指定的線程執(zhí)行完畢。底層調用的是Object實例的wait方法;LockSupport.park():在獲得調用權限之前禁止當前線程進行線程調度。
我們主要解釋Object.wait()和Thread.join()的用法。
繼續(xù)前面的例子來解釋 WAITING 狀態(tài):
你在銀行等了很久,終于輪到你來辦理業(yè)務了。但不幸的是,你到達柜臺后,柜臺的電腦突然壞了。你必須等待維修人員修好電腦后才能繼續(xù)辦理業(yè)務。
此時,假設你是線程 A,維修人員是線程 B。雖然你已經擁有了鎖(窗口),但你仍然需要釋放鎖。此時,線程 A 的狀態(tài)是 WAITING,然后線程 B 獲得鎖并進入 RUNNABLE 狀態(tài)。
如果線程 B 沒有主動喚醒線程 A(通過
notify()或notifyAll()),線程 A 只能一直等待。
Object.wait()
對于這個例子,我們使用wait()、notify()實現(xiàn),如下所示:
public class WaitingCase {
    private synchronized void businessProcessing() {
        try {
            System.out.println("Thread[" + Thread.currentThread().getName() + "] 處理業(yè)務,但電腦壞了。");
            // 釋放窗口資源(鎖)
            wait();
            // 業(yè)務處理
            System.out.println("Thread[" + Thread.currentThread().getName() + "] 繼續(xù)處理業(yè)務。");
            Thread.sleep(2000L);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    private synchronized void repairComputer() {
        System.out.println("Thread[" + Thread.currentThread().getName() + "] 維修電腦。");
        try {
            // 模擬維修
            Thread.sleep(1000);
            System.out.println("Thread[" + Thread.currentThread().getName() + "] 電腦維修好了。");
            notify();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    public static void main(String[] args) throws InterruptedException {
        WaitingCase blockedCase = new WaitingCase();
        Thread A = new Thread(blockedCase::businessProcessing, "A");
        Thread B = new Thread(blockedCase::repairComputer, "B");
        A.start();
        Thread.sleep(500); // 用于確保線程 A 先搶到鎖。睡眠時間應該小于維修時間
        B.start();
        System.out.println("Thread[" + A.getName() + "] state:" + A.getState());
        System.out.println("Thread[" + B.getName() + "] state:" + B.getState());
    }
}輸出:
Thread[A] 處理業(yè)務,但電腦壞了。
Thread[B] 維修電腦。
Thread[A] state:WAITING
Thread[B] state:TIMED_WAITING
Thread[B] 電腦維修好了。
Thread[A] 繼續(xù)處理業(yè)務。關于wait()方法,這里有一些需要注意的點:
- 線程在調用
wait()方法之前必須持有對象的鎖。 - 當線程調用
wait()方法時,它會釋放當前的鎖,直到另一個線程調用notify()或notifyAll()方法喚醒等待鎖的線程。 - 調用
notify()方法只會喚醒一個等待鎖的線程。如果有多個線程在等待鎖,之前調用wait()方法的線程可能不會被喚醒。 - 調用
notifyAll()方法后,所有等待鎖的線程都會被喚醒,但時間片可能不會立即分配給剛剛放棄鎖的線程,這取決于系統(tǒng)的調度。 
Thread.join()
join()方法暫停調用線程的執(zhí)行,直到被調用的對象完成執(zhí)行。此時,當前線程處于WAITING狀態(tài)。
join()方法通常在主線程中使用,以等待其他線程完成后主線程再繼續(xù)執(zhí)行。
現(xiàn)在來銀行辦理業(yè)務的人越來越多了,如果每次窗口空閑出來后所有人都會爭搶窗口的話,會造成資源的浪費。
銀行想到了一個辦法。每個來辦理業(yè)務的客戶都會得到一個序列號,窗口會依次叫號。只有被叫到的客戶才需要去窗口,否則他們可以留在休息區(qū)。
讓我們擴展前面BlockCase中的例子來簡單實現(xiàn)這樣的功能:
public class JoinCase {
    private synchronized void businessProcessing() {
        try {
            System.out.println("Thread[" + Thread.currentThread().getName() + "] 辦理業(yè)務。");
            Thread.sleep(2000L);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    public static void main(String[] args) throws InterruptedException {
        JoinCase blockedCase = new JoinCase();
        Thread A = new Thread(blockedCase::businessProcessing, "A");
        Thread B = new Thread(blockedCase::businessProcessing, "B");
        Thread C = new Thread(blockedCase::businessProcessing, "C");
        System.out.println("請讓線程 A 到窗口處理業(yè)務。");
        A.start();
        A.join();
        System.out.println("請讓線程 B 到窗口處理業(yè)務。");
        B.start();
        B.join();
        System.out.println("請讓線程 C 到窗口處理業(yè)務。");
        C.start();
    }
}輸出:
請讓線程 A 到窗口處理業(yè)務。
Thread[A] 辦理業(yè)務。
請讓線程 B 到窗口處理業(yè)務。
Thread[B] 辦理業(yè)務。
請讓線程 C 到窗口處理業(yè)務。
Thread[C] 辦理業(yè)務。你可以多次嘗試執(zhí)行這個程序,每次都會得到相同的結果。
TIMED_WAITING(超時等待)
超時等待狀態(tài)。線程等待特定的時間,時間到了會自動喚醒??闪鬓D狀態(tài):RUNNABLE
調用以下方法會使線程進入超時等待狀態(tài):
Thread.sleep(long millis):使當前線程睡眠指定的時間,不釋放鎖;Object.wait(long timeout):線程等待指定的時間。在等待期間,可以通過notify()/notifyAll()喚醒;Thread.join(long millis):等待指定線程執(zhí)行最多millis毫秒。如果millis為 0,則會繼續(xù)執(zhí)行;LockSupport.parkNanos(long nanos):在獲得調用權限之前,禁止當前線程進行線程調度指定的納秒時間;LockSupport.parkUntil(long deadline):與上述類似,也禁止線程調度指定的時間。
我們繼續(xù)上面的例子來解釋 TIMED_WAITING 狀態(tài):
當你輪到你辦理業(yè)務員時,之前辦理業(yè)務的客戶說他忘記處理一個業(yè)務,現(xiàn)在需要處理,要求你給他 5 分鐘時間。你同意了然后就去休息區(qū)休息,當 5 分鐘過去后,你重新去辦理業(yè)務。
此時,你仍然是線程 A,插隊的朋友是線程 B。線程 B 讓線程 A 等待指定的時間,在這段等待期間,A 處于 TIMED_WAITING 狀態(tài)。
等待 5 分鐘后,A 自動喚醒,獲得了競爭鎖(窗口)的資格。
可以使用Object.wait(long timeout)方法實現(xiàn)。Object.wait(long timeout)方法與無參數的wait()方法功能相同,都可以被其他線程調用notify()或notifyAll()方法喚醒。
public class TimedWaitingCase {
    privatestaticfinal Object lock = new Object();
    public static void main(String[] args) {
        // 線程 A:模擬等待超時
        Thread threadA = new Thread(() -> {
            synchronized (lock) {
                try {
                    System.out.println("線程 A 開始等待,最多等待 5 秒...");
                    // 線程 A 進入 TIMED_WAITING 狀態(tài),等待 5 秒
                    lock.wait(5000);
                    System.out.println("線程 A 等待結束,繼續(xù)執(zhí)行。");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        // 線程 B:模擬在等待期間喚醒線程 A
        Thread threadB = new Thread(() -> {
            synchronized (lock) {
                try {
                    // 線程 B 先睡眠 2 秒,模擬一些處理時間
                    Thread.sleep(2000);
                    System.out.println("線程 B 嘗試喚醒等待的線程 A...");
                    // 喚醒等待的線程 A
                    lock.notify();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        // 啟動線程 A
        threadA.start();
        // 啟動線程 B
        threadB.start();
    }
}不同之處在于,帶參數的wait(long)方法即使沒有其他線程喚醒它,也會在指定時間后自動喚醒,使其獲得競爭鎖的資格。
TERMINATED(終止)
再來看看最后一種狀態(tài),Terminated終止狀態(tài),要想進入這個狀態(tài)有兩種可能。
run()方法執(zhí)行完畢,線程正常退出。- 出現(xiàn)一個沒有捕獲的異常,終止了
run()方法,最終導致意外終止。 
可流轉狀態(tài):無
總結
Java 線程的六種狀態(tài)(NEW、RUNNABLE、BLOCKED、WAITING、TIMED_WAITING、TERMINATED)描述了線程從創(chuàng)建到終止的完整生命周期。理解這些狀態(tài)及其轉換機制,有助于更好地掌握多線程編程,避免常見的并發(fā)問題。Java 線程狀態(tài)與操作系統(tǒng)線程狀態(tài)雖有相似之處,但 Java 對其進行了更細粒度的劃分,以適應復雜的并發(fā)場景。掌握這些狀態(tài)及其轉換,是編寫高效、穩(wěn)定多線程程序的關鍵。















 
 
 












 
 
 
 