引言
各位開發者好!在上一篇文章中,我們詳細介紹了 Java 多線程的四種創建方式。今天,我們將深入探討線程的生命週期和基礎操作方法,這些知識對於理解多線程程序的行為和調試線程問題至關重要。
很多初學者在多線程編程中遇到的困惑,往往源於對線程狀態轉換和控制方法的理解不足。比如:為什麼我的線程沒有執行?為什麼線程無法停止?如何優雅地結束一個線程?今天,我們就來一一解答這些問題。
一、線程的六大狀態詳解
Java 中的 Thread 類定義了線程的六種狀態,這些狀態定義在 Thread 類的內部枚舉 State 中:
public enum State {
NEW,
RUNNABLE,
BLOCKED,
WAITING,
TIMED_WAITING,
TERMINATED;
}
下面我們詳細解析每一種狀態及其轉換過程:
1. NEW(新建)
當你創建一個 Thread 對象但還沒有調用 start()方法時,線程處於 NEW 狀態。
Thread thread = new Thread(() -> {
System.out.println("線程任務執行");
});
thread.setName("worker-thread"); // 給線程起一個有意義的名稱
System.out.println("創建後線程狀態:" + thread.getState()); // 輸出: NEW
2. RUNNABLE(可運行)
調用 start()方法後,線程進入 RUNNABLE 狀態。在 Java 中,RUNNABLE 狀態包含了操作系統層面的"就緒"和"運行中"兩個狀態:
- 就緒:線程已經準備好運行,但等待 CPU 分配時間片
- 運行中:線程正在 CPU 上執行
重要説明:Java 中的 RUNNABLE 狀態是一個複合狀態,不區分線程是"就緒"還是"正在運行",這與操作系統的線程狀態模型不同。即使線程獲得了 CPU 時間片正在執行,在 Java API 看來它仍然是 RUNNABLE 狀態,無法通過 Thread.getState()區分線程是否正在 CPU 上執行。
thread.start();
System.out.println("啓動後線程狀態:" + thread.getState()); // 輸出: RUNNABLE
3. BLOCKED(阻塞)
線程被阻塞,等待獲取一個 synchronized 內置鎖(也稱 monitor 鎖)。當線程嘗試進入一個 synchronized 塊/方法,但該鎖被其他線程持有時,就會進入這個狀態。
Object lock = new Object();
Thread thread1 = new Thread(() -> {
synchronized (lock) {
try {
Thread.sleep(3000); // 持有鎖3秒
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
thread1.setName("lock-holder");
Thread thread2 = new Thread(() -> {
synchronized (lock) {
System.out.println("線程2獲取到了鎖");
}
});
thread2.setName("lock-waiter");
thread1.start(); // 先啓動線程1
Thread.sleep(100); // 確保線程1先獲取到鎖
thread2.start(); // 再啓動線程2
Thread.sleep(100); // 給線程2一點時間嘗試獲取鎖
System.out.println("線程2狀態:" + thread2.getState()); // 輸出: BLOCKED
4. WAITING(等待)
線程進入無限期等待狀態,需要其他線程執行特定操作後才能繼續。
主要由以下方法導致:
- Object.wait()
- Thread.join()
- LockSupport.park()
特別説明:Object.wait()方法會釋放持有的 monitor 鎖(也就是 synchronized 鎖),而 LockSupport.park()不會釋放任何鎖,這是一個重要區別。
性能考慮:在高併發環境中,過多線程進入 WAITING 狀態可能導致系統資源浪費。建議使用帶超時參數的 wait(timeout),避免由於通知丟失導致線程永久等待。
Object lock = new Object();
Thread waitingThread = new Thread(() -> {
synchronized (lock) {
try {
lock.wait(); // 進入WAITING狀態,同時釋放鎖
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
waitingThread.setName("waiting-thread");
waitingThread.start();
Thread.sleep(100); // 確保waitingThread進入等待狀態
System.out.println("等待中的線程狀態:" + waitingThread.getState()); // 輸出: WAITING
5. TIMED_WAITING(計時等待)
與 WAITING 類似,但有超時時間。以下方法會導致這個狀態:
- Thread.sleep(long)
- Object.wait(long)
- Thread.join(long)
- LockSupport.parkNanos()
- LockSupport.parkUntil()
Thread sleepingThread = new Thread(() -> {
try {
Thread.sleep(5000); // 休眠5秒,進入TIMED_WAITING狀態
} catch (InterruptedException e) {
e.printStackTrace();
}
});
sleepingThread.setName("sleeping-thread");
sleepingThread.start();
Thread.sleep(100); // 確保sleepingThread進入休眠狀態
System.out.println("休眠中的線程狀態:" + sleepingThread.getState()); // 輸出: TIMED_WAITING
6. TERMINATED(終止)
線程執行完畢或因異常結束。
Thread terminatedThread = new Thread(() -> {
// 執行一些簡短的任務
System.out.println("任務執行完畢");
});
terminatedThread.setName("terminated-thread");
terminatedThread.start();
Thread.sleep(100); // 確保線程有足夠時間完成任務
System.out.println("任務完成後線程狀態:" + terminatedThread.getState()); // 輸出: TERMINATED
三種等待狀態的對比
| 狀態 | 觸發條件 | 是否釋放鎖 | 恢復條件 | 典型使用場景 |
|---|---|---|---|---|
| BLOCKED | 等待進入 synchronized 同步塊/方法 | 不持有鎖 | 獲得鎖 | 多線程競爭共享資源 |
| WAITING | Object.wait()
Thread.join() LockSupport.park() |
wait()釋放鎖
join/park 不涉及鎖釋放 |
notify/notifyAll
目標線程結束 unpark |
線程協作,等待條件滿足 |
| TIMED_WAITING | Thread.sleep(time)
Object.wait(time) Thread.join(time) |
sleep 不釋放鎖
wait 釋放鎖 join 不涉及鎖釋放 |
時間到期或上述對應條件 | 超時等待,避免無限阻塞 |
二、start()與 run()的本質區別
初學者常犯的一個錯誤是直接調用線程的 run()方法,而不是 start()方法。這兩者有本質區別:
調用 run()
Thread thread = new Thread(() -> {
System.out.println("當前線程: " + Thread.currentThread().getName());
});
thread.run(); // 直接調用run方法
輸出:當前線程: main
調用 start()
Thread thread = new Thread(() -> {
System.out.println("當前線程: " + Thread.currentThread().getName());
});
thread.start(); // 調用start方法
輸出:當前線程: Thread-0
區別分析
- run(): 普通方法調用,在當前線程(通常是 main 線程)執行線程體,沒有創建新線程
- start(): 啓動新線程,在新線程中執行 run()方法,實現了多線程併發執行
底層原理:start()方法會調用 native 方法 start0(),該方法會在 JVM 層面創建一個新的操作系統線程,並設置線程狀態,最終導致 run()方法在新線程中執行。
常見錯誤案例
// 錯誤用法:重複調用start()
Thread thread = new Thread(() -> System.out.println("任務執行"));
thread.start();
thread.start(); // 拋出IllegalThreadStateException
// 錯誤理解:以為run()會啓動線程
Thread thread2 = new Thread(() -> {
for(int i=0; i<1000; i++) {
System.out.println(i);
}
});
thread2.run(); // 在主線程中順序執行,沒有併發效果
三、線程控制方法詳解
Java 提供了幾個重要的線程控制方法,下面我們來詳細解析:
1. sleep() - 線程休眠
使當前線程暫停執行指定的時間,進入 TIMED_WAITING 狀態,但不會釋放鎖。
public static void sleepDemo() {
Object lock = new Object();
Thread thread = new Thread(() -> {
synchronized (lock) {
System.out.println("線程獲取到鎖");
try {
System.out.println("線程開始休眠5秒");
Thread.sleep(5000);
System.out.println("線程休眠結束");
} catch (InterruptedException e) {
System.out.println("線程被中斷");
}
}
});
thread.setName("sleeping-thread");
thread.start();
// 給點時間讓線程啓動並獲取鎖
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 嘗試獲取鎖,會被阻塞直到上面的線程釋放鎖
synchronized (lock) {
System.out.println("主線程獲取到鎖");
}
}
要點:
- sleep 是 Thread 類的靜態方法,會暫停當前正在執行的線程
- 不會釋放鎖資源
- 可以被 interrupt()方法中斷,拋出 InterruptedException
- sleep 結束後線程會自動回到 RUNNABLE 狀態
- 長時間的 sleep()會佔用線程資源而不執行工作,在線程池環境中可能導致性能下降
- 建議配合合理的超時機制使用,避免無限期阻塞
2. yield() - 線程讓步
提示調度器當前線程願意放棄 CPU 使用權,但調度器可以忽略這個提示。
public static void yieldDemo() {
Thread thread1 = new Thread(() -> {
for (int i = 0; i < 100; i++) {
System.out.println("線程1: " + i);
if (i % 10 == 0) {
System.out.println("線程1讓步");
Thread.yield();
}
}
});
thread1.setName("yield-thread");
Thread thread2 = new Thread(() -> {
for (int i = 0; i < 100; i++) {
System.out.println("線程2: " + i);
}
});
thread2.setName("normal-thread");
thread1.start();
thread2.start();
}
要點:
- yield 是 Thread 類的靜態方法
- 只是提示調度器,沒有強制性
- 從運行狀態到就緒狀態的轉變(仍然是 RUNNABLE)
- 實際效果取決於操作系統的實現,不可靠
yield()在現代系統中的實際效果
儘管 yield()方法的理論目的是讓出 CPU 時間片,但在現代操作系統和 JVM 實現中,它的實際效果往往不可預測:
- 大多數現代 CPU 調度器已經非常智能,能夠有效分配時間片
- 不同 JVM 實現和操作系統對 yield()的處理方式不同
- 在某些系統上,yield()可能完全沒有效果
- 在其他系統上,yield()可能導致當前線程被過度懲罰,長時間無法獲得 CPU
在實際開發中:
- 避免使用 yield()來解決線程協作問題
- 如需控制線程執行順序,應使用顯式同步機制(如 CountDownLatch、CyclicBarrier 等)
- 如需控制執行時間分配,考慮使用線程優先級或更高級的調度框架
3. join() - 線程等待
讓當前線程等待另一個線程執行完畢後再繼續執行。
public static void joinDemo() {
Thread worker = new Thread(() -> {
System.out.println("工作線程開始執行...");
try {
// 模擬耗時操作
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("工作線程執行完畢");
});
worker.setName("worker-thread");
System.out.println("主線程啓動工作線程");
worker.start();
System.out.println("主線程等待工作線程完成");
try {
worker.join(); // 主線程在這裏等待worker線程執行完畢
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("主線程繼續執行");
}
join()方法的重載版本:
join(): 等待線程終止join(long millis): 等待指定的毫秒數join(long millis, int nanos): 等待指定的毫秒數加納秒數
join()方法的內部實現原理:
// join()方法的內部實現原理(簡化版)
public final synchronized void join(long millis) throws InterruptedException {
if (millis > 0) {
if (isAlive()) {
// 當前線程在目標線程對象上等待
wait(millis);
}
} else if (millis == 0) {
while (isAlive()) {
// 無限期等待
wait(0);
}
}
// 注意:當目標線程終止時,JVM會調用notifyAll()喚醒所有等待線程
}
深入理解:當線程 A 調用線程 B 的 join()方法時,線程 A 會在線程 B 對象的監視器上等待。當線程 B 執行完畢(無論正常結束還是異常結束),JVM 會調用線程 B 對象的 notifyAll()方法,從而喚醒在其上等待的線程 A。這種設計確保了線程 A 能夠在線程 B 結束後繼續執行。
性能考慮:
- 無限期的 join()可能導致調用線程長時間等待,降低系統吞吐量
- 在線程池環境中,線程長時間阻塞會降低線程池效率
- 推薦使用 join(timeout)設置合理的等待超時時間
4. interrupt() - 線程中斷
這是一種協作式的線程中斷機制,用於通知線程應該停止或中斷當前工作。
public static void interruptDemo() {
Thread sleepingThread = new Thread(() -> {
try {
System.out.println("線程開始休眠10秒");
Thread.sleep(10000);
System.out.println("休眠完成"); // 如果被中斷,這行不會執行
} catch (InterruptedException e) {
System.out.println("線程被中斷: " + e.getMessage());
} finally {
System.out.println("線程結束");
}
});
sleepingThread.setName("interrupted-thread");
sleepingThread.start();
// 主線程休眠2秒後中斷sleepingThread
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("主線程發出中斷請求");
sleepingThread.interrupt();
}
中斷機制相關方法:
interrupt(): 請求中斷線程isInterrupted(): 檢查線程是否被中斷(不清除中斷狀態)Thread.interrupted(): 靜態方法,檢查當前線程是否被中斷(清除中斷狀態)
非阻塞場景的中斷處理
在非阻塞狀態下,interrupt()方法只會設置線程的中斷標誌,不會導致線程拋出 InterruptedException。此時,線程需要主動檢查中斷狀態並作出響應:
public static void nonBlockingInterruptDemo() {
Thread worker = new Thread(() -> {
// 給線程起一個有意義的名稱
Thread.currentThread().setName("worker-thread");
// 執行計算密集型任務,定期檢查中斷狀態
long sum = 0;
System.out.println(Thread.currentThread().getName() + " 開始計算");
while (!Thread.currentThread().isInterrupted()) {
// 執行非阻塞計算
for (int i = 0; i < 1_000_000; i++) {
sum += i;
}
System.out.println("計算結果: " + sum);
sum = 0;
}
System.out.println(Thread.currentThread().getName() + " 檢測到中斷信號,優雅退出");
});
worker.start();
try {
Thread.sleep(100); // 讓worker線程有時間開始工作
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("主線程發送中斷請求");
worker.interrupt();
}
自定義阻塞方法的中斷處理
當使用顯式鎖(如 ReentrantLock)或自定義阻塞機制時,需要特別注意中斷處理:
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.locks.Condition;
public static void customBlockingInterruptDemo() {
Lock lock = new ReentrantLock();
Condition condition = lock.newCondition();
Thread waitingThread = new Thread(() -> {
try {
// 可中斷的鎖獲取
lock.lockInterruptibly();
try {
System.out.println("線程獲取到鎖,等待條件");
// 等待條件,可被中斷
condition.await();
System.out.println("條件滿足,繼續執行");
} finally {
lock.unlock();
}
} catch (InterruptedException e) {
System.out.println("線程在等待鎖或條件時被中斷: " + e.getMessage());
}
});
waitingThread.setName("custom-waiting-thread");
waitingThread.start();
try {
Thread.sleep(1000); // 確保等待線程已經進入等待狀態
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("主線程發送中斷請求");
waitingThread.interrupt();
}
中斷機制的核心概念:
- interrupt()方法是一種協作式而非強制式的線程中斷機制
- 線程可以選擇如何響應中斷請求,甚至可以完全忽略它
- 良好的設計應確保線程能夠及時檢查並響應中斷請求
isInterrupted() vs Thread.interrupted():
public static void interruptFlags() {
Thread thread = new Thread(() -> {
while (!Thread.currentThread().isInterrupted()) {
System.out.println("線程工作中...");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// sleep()方法被中斷會清除中斷標誌
// 需要重新設置中斷標誌位以便外層循環檢測
Thread.currentThread().interrupt();
System.out.println("中斷髮生,退出循環");
break;
}
}
});
thread.start();
// 主線程休眠3秒後中斷工作線程
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
thread.interrupt();
}
正確處理 InterruptedException 的模式
public void run() {
try {
while (!Thread.currentThread().isInterrupted()) {
// 執行任務
// 執行可能被中斷的阻塞操作
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// 重新設置中斷標誌並退出循環
Thread.currentThread().interrupt();
break;
}
}
} finally {
// 執行清理工作
System.out.println("線程結束,執行清理");
}
}
四、守護線程(Daemon Thread)
守護線程是一種特殊的線程,它在後台為其他非守護線程提供服務。當所有非守護線程結束時,無論守護線程是否完成工作,JVM 都會退出。
守護線程的特點
- 當 JVM 中只剩下守護線程時,JVM 會退出
- 必須在線程啓動前設置守護狀態
- 典型應用:垃圾回收器、監控線程、心跳線程等
守護線程示例
public static void daemonDemo() {
Thread daemonThread = new Thread(() -> {
while (true) {
try {
System.out.println("守護線程工作中...");
Thread.sleep(1000);
} catch (InterruptedException e) {
break;
}
}
});
daemonThread.setName("daemon-thread");
// 設置為守護線程(必須在start之前)
daemonThread.setDaemon(true);
daemonThread.start();
// 主線程工作3秒
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("主線程結束,程序將退出");
// 程序退出,守護線程也會終止
}
為何守護線程的 finally 塊不保證執行?
當 JVM 準備退出時,如果只剩下守護線程,JVM 會強制終止這些線程而不等待它們完成當前工作。這意味着:
public static void daemonFinalizationDemo() {
Thread daemon = new Thread(() -> {
try {
System.out.println("守護線程開始執行");
Thread.sleep(5000); // 模擬長時間操作
System.out.println("守護線程完成工作"); // 可能不會執行
} catch (InterruptedException e) {
System.out.println("守護線程被中斷");
} finally {
System.out.println("守護線程的finally塊"); // 可能不會執行
// 關鍵資源清理如文件關閉、連接釋放可能不會發生
}
});
daemon.setDaemon(true);
daemon.start();
// 主線程很快結束
System.out.println("主線程結束,JVM準備退出");
}
守護線程應用場景
- 日誌收集線程:週期性地將日誌刷新到磁盤
- 緩存清理線程:後台清理過期緩存
- 心跳檢測線程:定期發送心跳包
- 服務監控線程:監控服務健康狀態
守護線程注意事項
- 守護線程創建的線程也是守護線程
- 守護線程不應該用於執行 I/O 操作,因為它們可能在操作完成前被強制終止
- finally 塊在守護線程中不保證一定會執行
- 不要將連接池或事務性操作放在守護線程中
實踐建議:不要在守護線程中執行以下操作:
- 文件或數據庫操作(可能導致數據損壞)
- 網絡連接管理(可能導致連接泄漏)
- 事務處理(可能導致事務不完整)
五、線程常見問題及解決方案
1. 線程執行不成功
問題描述:創建了線程對象,但沒有執行任務
常見原因:
- 調用 run()而不是 start()
- 線程對象被垃圾回收
- 主線程結束太快
解決方案:
// 正確啓動線程
Thread thread = new Thread(() -> {
System.out.println("任務執行");
});
thread.start(); // 不是thread.run()
// 保持對線程的引用
// 如果需要,可以使用join等待線程完成
2. 線程無法停止
問題描述:嘗試停止一個正在運行的線程
常見錯誤:使用已廢棄的 stop()、suspend()方法
為什麼不應使用 stop()方法?
Thread.stop()方法在 Java 1.2 版本就被標記為廢棄,主要原因包括:
- 不安全的資源釋放:stop()會立即終止線程,不給線程任何機會執行清理工作,可能導致:
- 文件句柄未關閉
- 數據庫連接未釋放
- 網絡套接字未關閉
- 臨時文件未刪除
- 數據不一致:強制終止可能使對象處於不一致狀態
// 假設有轉賬操作
synchronized void transfer(Account from, Account to, int amount) {
from.debit(amount); // 假如在這行之後被stop()
to.credit(amount); // 這行永遠不會執行
// 結果:錢從from賬户扣除了,但沒有加到to賬户
}
- 鎖狀態不確定:線程被 stop()時會釋放所有鎖,但可能導致受保護數據的不一致狀態暴露給其他線程
推薦解決方案:使用中斷機制或狀態標誌位
// 通過標誌位控制線程結束
class StoppableTask implements Runnable {
private volatile boolean stopRequested = false;
public void requestStop() {
stopRequested = true;
}
@Override
public void run() {
while (!stopRequested) {
// 執行任務
System.out.println("線程工作中...");
try {
Thread.sleep(500);
} catch (InterruptedException e) {
// 響應中斷
Thread.currentThread().interrupt(); // 重設中斷標誌
break;
}
}
System.out.println("線程正常退出");
}
}
// 使用示例
public static void stoppableThreadDemo() {
StoppableTask task = new StoppableTask();
Thread thread = new Thread(task);
thread.start();
// 主線程工作3秒
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 請求線程停止
task.requestStop();
}
3. 死鎖問題
問題描述:兩個或多個線程互相等待對方持有的鎖,形成環路等待
死鎖示例:
public static void deadlockDemo() {
Object resource1 = new Object();
Object resource2 = new Object();
Thread thread1 = new Thread(() -> {
synchronized (resource1) {
System.out.println("線程1獲取資源1");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("線程1等待資源2");
synchronized (resource2) {
System.out.println("線程1獲取資源2");
}
}
});
thread1.setName("deadlock-thread-1");
Thread thread2 = new Thread(() -> {
synchronized (resource2) {
System.out.println("線程2獲取資源2");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("線程2等待資源1");
synchronized (resource1) {
System.out.println("線程2獲取資源1");
}
}
});
thread2.setName("deadlock-thread-2");
thread1.start();
thread2.start();
}
其他常見併發問題
除了死鎖外,多線程編程中還存在以下併發問題:
線程飢餓(Starvation)
當線程長時間無法獲取所需資源而無法執行時,就會發生線程飢餓:
public static void starvationDemo() {
Object lock = new Object();
// 創建一個高優先級線程,它會長時間佔用鎖
Thread highPriorityThread = new Thread(() -> {
synchronized (lock) {
System.out.println("高優先級線程獲取到鎖");
while (true) {
// 持續佔用鎖不釋放
}
}
});
highPriorityThread.setPriority(Thread.MAX_PRIORITY);
highPriorityThread.setName("high-priority");
// 創建低優先級線程,它很難獲取到鎖
Thread lowPriorityThread = new Thread(() -> {
synchronized (lock) {
//
System.out.println("低優先級線程獲取到鎖");
}
});
lowPriorityThread.setPriority(Thread.MIN_PRIORITY);
lowPriorityThread.setName("low-priority");
highPriorityThread.start();
lowPriorityThread.start();
}
避免飢餓的策略:
- 使用公平鎖(如 ReentrantLock(true))
- 避免長時間持有鎖
- 適當調整線程優先級
注意:在現代 JVM 中,線程優先級的效果可能不如預期。優先級調整是一種建議而非強制執行的機制,不同操作系統實現差異很大。推薦優先通過合理的鎖使用策略(減少鎖持有時間、使用公平鎖等)來避免線程飢餓,而不是過度依賴優先級調整。
活鎖(Livelock)
當線程不斷相互禮讓,都無法向前推進時,就會發生活鎖:
public static void simpleLivelockDemo() {
final AtomicBoolean firstThreadGiveWay = new AtomicBoolean(true);
final AtomicBoolean secondThreadGiveWay = new AtomicBoolean(true);
Thread thread1 = new Thread(() -> {
while (true) {
// 第一個線程檢查"對方是否在讓路"
if (secondThreadGiveWay.get()) {
// 如果對方在讓路,自己也讓路
System.out.println("線程1説:對方在讓路,我也讓路");
firstThreadGiveWay.set(true);
} else {
// 如果對方不讓路,自己也不讓了,開始工作
System.out.println("線程1説:對方不讓路了,我開始工作");
break;
}
try {
Thread.sleep(100);
} catch (InterruptedException e) {
return;
}
}
});
Thread thread2 = new Thread(() -> {
while (true) {
// 第二個線程檢查"對方是否在讓路"
if (firstThreadGiveWay.get()) {
// 如果對方在讓路,自己也讓路
System.out.println("線程2説:對方在讓路,我也讓路");
secondThreadGiveWay.set(true);
} else {
// 如果對方不讓路,自己也不讓了,開始工作
System.out.println("線程2説:對方不讓路了,我開始工作");
break;
}
try {
Thread.sleep(100);
} catch (InterruptedException e) {
return;
}
}
});
thread1.start();
thread2.start();
// 這個活鎖將永遠持續下去,因為兩個線程都在互相禮讓
// 在實際應用中,可以通過隨機等待或優先級解決
}
避免活鎖的策略:
- 引入隨機因素(如隨機等待時間)
- 使用優先級或資源排序
- 設置超時機制
4. 線程異常處理
線程中未捕獲的異常不會被傳遞到主線程,需要特殊處理:
public static void exceptionHandlerDemo() {
Thread thread = new Thread(() -> {
System.out.println("線程開始執行");
throw new RuntimeException("線程執行異常");
});
thread.setName("exception-thread");
// 設置未捕獲異常處理器
thread.setUncaughtExceptionHandler((t, e) -> {
System.out.println("捕獲到線程 " + t.getName() + " 的異常:" + e.getMessage());
// 可以記錄日誌、發送告警等
});
thread.start();
}
六、總結
| 類別 | 方法/狀態 | 特點 | 注意事項 |
|---|---|---|---|
| 線程狀態 | NEW | 線程已創建未啓動 | 使用 start()方法啓動 |
| RUNNABLE | 包含就緒和運行中 | 線程獲得 CPU 時間片才真正運行 | |
| BLOCKED | 等待獲取 synchronized 鎖 | 避免長時間阻塞 | |
| WAITING | 無限期等待其他線程操作 | 可能導致程序掛起 | |
| TIMED_WAITING | 有超時時間的等待 | 設置合理的超時時間 | |
| TERMINATED | 線程執行完畢 | 可以用 isAlive()檢查 | |
| 線程控制 | start() | 啓動新線程 | 不能重複調用 |
| run() | 普通方法調用,不創建新線程 | 不要直接調用 run()方法 | |
| sleep() | 線程休眠,不釋放鎖 | 處理 InterruptedException | |
| yield() | 線程讓步,提示調度器 | 效果不可靠,依賴系統實現 | |
| join() | 等待其他線程完成 | 可能導致當前線程阻塞 | |
| interrupt() | 中斷線程,協作式機制 | 結合 isInterrupted()使用 | |
| 線程類型 | 用户線程 | 默認線程類型 | JVM 等待用户線程結束 |
| 守護線程 | 為其他線程服務的後台線程 | 不執行重要任務,finally 塊不保證執行 | |
| 併發問題 | 死鎖 | 線程互相等待對方持有的鎖 | 按固定順序獲取鎖,使用 tryLock() |
| 活鎖 | 線程不斷相互禮讓 | 引入隨機因素,設置優先級 | |
| 飢餓 | 線程長期無法獲取資源 | 使用公平鎖,避免長時間持有鎖 |
通過本文,我們深入瞭解了 Java 線程的生命週期和基礎操作方法。掌握這些知識對於編寫高質量的多線程程序至關重要。在實際開發中,請記住:理解線程狀態轉換、合理使用線程控制方法、妥善處理線程異常、選擇適當的線程通信機制,這些都是構建穩定多線程應用的基石。
在下一篇文章中,我們將探討線程安全問題與基本解決方案,敬請期待!
感謝您耐心閲讀到這裏!如果覺得本文對您有幫助,歡迎點贊 👍、收藏 ⭐、分享給需要的朋友,您的支持是我持續輸出技術乾貨的最大動力!
如果想獲取更多 Java 技術深度解析,歡迎點擊頭像關注我,後續會每日更新高質量技術文章,陪您一起進階成長~