Java 線程生命周期與狀態(tài)轉(zhuǎn)換的核心路徑
一、前言
在上一篇文章中,我們學習了 Java 中創(chuàng)建線程的 5 種方式,掌握了線程的基礎創(chuàng)建與啟動邏輯。但線程從創(chuàng)建到銷毀并非一成不變,而是會在不同狀態(tài)之間轉(zhuǎn)換。理解線程的生命周期和狀態(tài)轉(zhuǎn)換,是解決多線程并發(fā)問題的關鍵。
本文將從理論到實踐,深入解析 Java 線程的 6 種狀態(tài),演示狀態(tài)轉(zhuǎn)換的核心路徑,并通過工具分析線程狀態(tài),幫助大家徹底掌握這一核心知識點。
二、Java 線程的 6 種狀態(tài)
Java 線程的狀態(tài)被明確枚舉在 Thread.State 中,共包含 6 種狀態(tài) ,任何時刻一個線程只能處于其中一種狀態(tài)。
狀態(tài)名稱 | 核心含義 | 進入條件 | 退出條件 |
|---|---|---|---|
NEW (新建) | 線程對象已創(chuàng)建,但未調(diào)用start()方法 | 創(chuàng)建 Thread 實例后,未執(zhí)行 start() | 調(diào)用 start() 方法 |
RUNNABLE (可運行) | 線程正在 CPU 上執(zhí)行,或處于就緒狀態(tài)等待 CPU 調(diào)度 | 調(diào)用 start() 方法;從其他阻塞 / 等待狀態(tài)喚醒 | 線程執(zhí)行完畢;進入阻塞 / 等待狀態(tài) |
BLOCKED (阻塞) | 線程因競爭鎖失敗而被阻塞 | 線程嘗試進入 synchronized 代碼塊 / 方法,但鎖被其他線程持有 | 成功獲取到鎖 |
WAITING (等待) | 線程無時間限制等待,需其他線程主動喚醒 | 調(diào)用 Object.wait()(無參)、Thread.join()(無參)、LockSupport.park() | 其他線程調(diào)用 Object.notify()/notifyAll()、被 join 的線程執(zhí)行完畢、調(diào)用 LockSupport.unpark() |
TIMED_WAITING (超時等待) | 線程有時間限制等待,超時后自動喚醒 | 調(diào)用 Thread.sleep(long)、Object.wait(long)、Thread.join(long)、LockSupport.parkNanos()/parkUntil() | 超時自動喚醒;其他線程提前喚醒 |
TERMINATED (終止) | 線程執(zhí)行完畢或異常退出 | run() 方法執(zhí)行完成;線程執(zhí)行過程中拋出未捕獲異常 | 無(線程生命周期結束) |
三、線程狀態(tài)轉(zhuǎn)換流程圖

四、線程狀態(tài)轉(zhuǎn)換
線程狀態(tài)轉(zhuǎn)換有固定的核心路徑,我們通過代碼案例逐一演示關鍵轉(zhuǎn)換場景,直觀觀察狀態(tài)變化。
前置說明
- 所有案例中,使用 Thread.getState() 方法獲取線程當前狀態(tài)。
- 為了精準觀察狀態(tài),使用 Thread.sleep() 控制線程執(zhí)行節(jié)奏,避免因 CPU 調(diào)度過快導致狀態(tài)無法捕捉。
場景 1:NEW → RUNNABLE → TERMINATED(基礎路徑)
核心邏輯 :線程創(chuàng)建后處于 NEW 狀態(tài),調(diào)用 start() 進入 RUNNABLE ,執(zhí)行完 run() 后進入 TERMINATED 。
public class ThreadStateDemo1 {
public static void main(String[] args) throws InterruptedException {
// 1. 創(chuàng)建線程,此時狀態(tài)為 NEW
Thread thread = new Thread(() -> {
System.out.println("線程執(zhí)行中,當前狀態(tài):" + Thread.currentThread().getState());
// 模擬任務執(zhí)行
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "基礎狀態(tài)線程");
System.out.println("調(diào)用start()前,線程狀態(tài):" + thread.getState()); // NEW
// 2. 啟動線程,進入 RUNNABLE 狀態(tài)
thread.start();
Thread.sleep(50); // 等待線程進入執(zhí)行狀態(tài)
System.out.println("線程執(zhí)行中,主線程觀察到的狀態(tài):" + thread.getState()); // RUNNABLE
// 3. 等待線程執(zhí)行完畢
thread.join();
System.out.println("線程執(zhí)行完畢,當前狀態(tài):" + thread.getState()); // TERMINATED
}
}運行結果
調(diào)用start()前,線程狀態(tài):NEW
線程執(zhí)行中,當前狀態(tài):RUNNABLE
線程執(zhí)行中,主線程觀察到的狀態(tài):RUNNABLE
線程執(zhí)行完畢,當前狀態(tài):TERMINATED
場景 2:RUNNABLE → BLOCKED → RUNNABLE(鎖競爭導致阻塞)
核心邏輯 :線程嘗試獲取 synchronized 鎖失敗時,進入 BLOCKED 狀態(tài);成功獲取鎖后,回到 RUNNABLE 。
public class ThreadStateDemo2 {
// 定義一個鎖對象
private static final Object LOCK = new Object();
public static void main(String[] args) throws InterruptedException {
// 線程1:先獲取鎖,持有鎖1秒
Thread thread1 = new Thread(() -> {
synchronized (LOCK) {
try {
System.out.println(Thread.currentThread().getName() + " 獲取到鎖,狀態(tài):RUNNABLE");
Thread.sleep(1000); // 持有鎖,讓線程2阻塞
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "線程1");
// 線程2:后獲取鎖,會進入 BLOCKED 狀態(tài)
Thread thread2 = new Thread(() -> {
synchronized (LOCK) {
System.out.println(Thread.currentThread().getName() + " 成功獲取鎖,狀態(tài)回到 RUNNABLE");
}
}, "線程2");
// 啟動線程1和線程2
thread1.start();
Thread.sleep(100); // 確保線程1先獲取鎖
thread2.start();
// 觀察線程2的狀態(tài)
Thread.sleep(200);
System.out.println(thread2.getName() + " 等待鎖,狀態(tài):" + thread2.getState()); // BLOCKED
// 等待線程1執(zhí)行完畢,線程2獲取鎖
thread1.join();
Thread.sleep(200);
System.out.println(thread2.getName() + " 執(zhí)行中,狀態(tài):" + thread2.getState()); // RUNNABLE
}
}運行結果
線程1 獲取到鎖,狀態(tài):RUNNABLE
線程2 等待鎖,狀態(tài):BLOCKED
線程2 成功獲取鎖,狀態(tài)回到 RUNNABLE
線程2 執(zhí)行中,狀態(tài):RUNNABLE
場景 3:RUNNABLE → WAITING → RUNNABLE(無參 wait () 喚醒)
核心邏輯 :線程調(diào)用 Object.wait() (無參)后,釋放鎖并進入 WAITING 狀態(tài);需其他線程調(diào)用 notify() / notifyAll() 喚醒,重新競爭鎖后回到 RUNNABLE 。
public class ThreadStateDemo3 {
private static final Object LOCK = new Object();
private static Thread waitThread; // 等待線程
public static void main(String[] args) throws InterruptedException {
// 等待線程:調(diào)用wait()進入WAITING狀態(tài)
waitThread = new Thread(() -> {
synchronized (LOCK) {
try {
System.out.println(Thread.currentThread().getName() + " 調(diào)用wait(),進入WAITING狀態(tài)");
LOCK.wait(); // 無參wait,釋放鎖
System.out.println(Thread.currentThread().getName() + " 被喚醒,狀態(tài)回到RUNNABLE");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "等待線程");
// 喚醒線程:調(diào)用notify()喚醒等待線程
Thread notifyThread = new Thread(() -> {
synchronized (LOCK) {
System.out.println(Thread.currentThread().getName() + " 獲取鎖,準備喚醒等待線程");
LOCK.notify(); // 喚醒等待線程
try {
Thread.sleep(500); // 持有鎖,讓等待線程先處于WAITING→BLOCKED
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "喚醒線程");
waitThread.start();
Thread.sleep(200);
System.out.println(waitThread.getName() + " 當前狀態(tài):" + waitThread.getState()); // WAITING
notifyThread.start();
notifyThread.join();
Thread.sleep(200);
System.out.println(waitThread.getName() + " 最終狀態(tài):" + waitThread.getState()); // RUNNABLE/TERMINATED
}
}運行結果
等待線程 調(diào)用wait(),進入WAITING狀態(tài)
等待線程 當前狀態(tài):WAITING
喚醒線程 獲取鎖,準備喚醒等待線程
等待線程 被喚醒,狀態(tài)回到RUNNABLE
等待線程 最終狀態(tài):TERMINATED
場景 4:RUNNABLE → TIMED_WAITING → RUNNABLE(sleep () 超時喚醒)
核心邏輯 :線程調(diào)用 Thread.sleep(long) 后,進入 TIMED_WAITING 狀態(tài);超時后自動喚醒,回到 RUNNABLE 狀態(tài)( sleep () 不會釋放鎖 )。
public class ThreadStateDemo4 {
public static void main(String[] args) throws InterruptedException {
Thread sleepThread = new Thread(() -> {
try {
System.out.println(Thread.currentThread().getName() + " 調(diào)用sleep(1000),進入TIMED_WAITING狀態(tài)");
Thread.sleep(1000); // 超時等待1秒
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " 超時喚醒,狀態(tài)回到RUNNABLE");
}, "睡眠線程");
sleepThread.start();
Thread.sleep(200);
System.out.println(sleepThread.getName() + " 當前狀態(tài):" + sleepThread.getState()); // TIMED_WAITING
sleepThread.join();
System.out.println(sleepThread.getName() + " 執(zhí)行完畢,狀態(tài):" + sleepThread.getState()); // TERMINATED
}
}運行結果
睡眠線程 調(diào)用sleep(1000),進入TIMED_WAITING狀態(tài)
睡眠線程 當前狀態(tài):TIMED_WAITING
睡眠線程 超時喚醒,狀態(tài)回到RUNNABLE
睡眠線程 執(zhí)行完畢,狀態(tài):TERMINATED
五、使用 jstack 命令分析線程狀態(tài)
在實際開發(fā)中,我們無法通過代碼實時觀察線上線程的狀態(tài),此時可以使用 JDK 自帶的 jstack 命令,查看線程的狀態(tài)和調(diào)用棧,定位線程阻塞、死鎖等問題。
1、操作步驟
1. 獲取進程 ID:使用 jps 命令,查看 Java 進程的 PID。
jps # 輸出示例:1234 ThreadStateDemo2 (1234為PID)
2. 導出線程快照:使用 jstack PID > thread_dump.txt 命令,將線程快照導出到文件。
jstack 1234 > thread_dump.txt
3. 分析線程狀態(tài):打開 thread_dump.txt 文件,搜索線程名稱,查看線程狀態(tài)。
2、實戰(zhàn)案例(分析 BLOCKED 狀態(tài)線程)
在 ThreadStateDemo2 運行時,導出線程快照,會看到類似如下內(nèi)容:
"線程2" #13 prio=5 os_prio=31 tid=0x00007f8b1282e000 nid=0x5003 waiting for monitor entry [0x0000700001a6f000]
java.lang.Thread.State: BLOCKED (on object monitor)
at ThreadStateDemo2.lambda$main$1(ThreadStateDemo2.java:18)
- waiting to lock <0x000000076ab81810> (a java.lang.Object)
- locked <0x000000076ab81810> (a java.lang.Object)- java.lang.Thread.State: BLOCKED (on object monitor):明確線程處于 BLOCKED 狀態(tài)。
- waiting to lock <0x000000076ab81810>:表示線程正在等待獲取該地址的鎖。
六、常見誤區(qū)辨析
- RUNNABLE 狀態(tài) ≠ 正在執(zhí)行:RUNNABLE 包含 “就緒” 和 “運行中” 兩種情況,只有獲取到 CPU 時間片的線程才會真正執(zhí)行。
- sleep () 和 wait () 的區(qū)別: sleep() 屬于 Thread 類,不會釋放鎖,超時自動喚醒; wait() 屬于 Object 類,會釋放鎖,需要其他線程喚醒。
- BLOCKED 和 WAITING 的區(qū)別: BLOCKED 是因競爭鎖阻塞, WAITING 是因調(diào)用 wait() / join() 等方法主動等待。
六、總結
本文詳細講解了 Java 線程的 6 種狀態(tài),通過 4 個代碼案例演示了狀態(tài)轉(zhuǎn)換的核心路徑,并介紹了使用 jstack 命令分析線程狀態(tài)的實戰(zhàn)方法。理解線程的生命周期和狀態(tài)轉(zhuǎn)換,是后續(xù)學習線程同步、線程通信的基礎。
下一篇文章,我們將深入分析并發(fā)編程的三大核心特性(原子性、可見性、有序性),并講解volatile關鍵字的原理與使用場景。
到此這篇關于Java 線程生命周期與狀態(tài)轉(zhuǎn)換的文章就介紹到這了,更多相關java線程生命周期內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
mybatisplus where QueryWrapper加括號嵌套查詢方式
這篇文章主要介紹了mybatisplus where QueryWrapper加括號嵌套查詢方式,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教。2022-01-01
Java使用ByteBuffer進行多文件合并和拆分的代碼實現(xiàn)
因為驗證證書的需要,需要把證書文件和公鑰給到客戶,考慮到多個文件交互的不便性,所以決定將2個文件合并成一個文件交互給客戶,但是由于是加密文件,采用字符串形式合并后,拆分后文件不可用,本文給大家介紹了Java使用ByteBuffer進行多文件合并和拆分,需要的朋友可以參考下2024-09-09
Java通過在主循環(huán)中判斷Boolean來停止線程的方法示例
這篇文章主要介紹了Java通過在主循環(huán)中判斷Boolean來停止線程的方法,結合具體實例形式分析了java針對線程的判斷與停止操作相關實現(xiàn)技巧,需要的朋友可以參考下2017-04-04
PowerJob的ProcessorLoader工作流程源碼解讀
這篇文章主要為大家介紹了PowerJob的ProcessorLoader工作流程源碼解讀,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2023-12-12
詳解Mybatis多參數(shù)傳遞入?yún)⑺姆N處理方式
這篇文章主要介紹了詳解Mybatis多參數(shù)傳遞入?yún)⑺姆N處理方式,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2021-04-04

