前言
多執行緒是 Java 核心特性之一。本文系統整理:執行緒建立兩種方式、生命週期狀態、同步化機制、wait/notify 協作模式、常見問題與最佳實踐。注意:Java 5+ 引入 java.util.concurrent 高階 API,實務開發優先使用 ExecutorService、Concurrent 集合、Lock、CountDownLatch 等,本文基礎 API 僅供理解底層原理。
建立執行緒兩種方式
方式一:繼承 Thread 類別
public class MyThread extends Thread {
@Override
public void run() {
// 執行緒工作內容
for (int i = 0; i < 5; i++) {
System.out.println(getName() + ": " + i);
}
}
}
// 使用
MyThread t1 = new MyThread();
t1.setName("Thread-A");
t1.start(); // 呼叫 start(),非 run()!
方式二:實作 Runnable 介面(推薦)
public class MyTask implements Runnable {
@Override
public void run() {
// 工作內容
}
}
// 使用
Thread t2 = new Thread(new MyTask(), "Thread-B");
t2.start();
// Java 8+ Lambda
Thread t3 = new Thread(() -> {
System.out.println("Lambda 執行緒");
}, "Thread-C");
t3.start();
比較
| 特性 | extends Thread | implements Runnable |
| 繼承限制 | 單一繼承,無法繼承其他類別 | 可繼承其他類別,彈性高 |
| 職責分離 | 執行緒控制與工作邏輯混在一起 | 工作邏輯獨立,可重複使用 |
| 執行緒池相容 | 不可直接提交 | 可提交給 ExecutorService |
執行緒生命週期狀態
NEW → RUNNABLE → RUNNING
↑ ↓
BLOCKED/WAITING/TIMED_WAITING
↓
TERMINATED
| 狀態 | 說明 | 進入原因 |
| NEW | 建立未啟動 | new Thread() |
| RUNNABLE | 就緒/執行中 | start()、時間片輪轉、喚醒 |
| BLOCKED | 阻塞等待監視器鎖 | 進入 synchronized 區塊被佔用 |
| WAITING | 無限期等待 | Object.wait()、Thread.join()、LockSupport.park() |
| TIMED_WAITING | 限時等待 | Thread.sleep()、wait(timeout)、join(timeout) |
| TERMINATED | 執行結束 | run() 返回、拋出未捕獲例外 |
常用控制方法
sleep() - 讓出 CPU、不釋放鎖
try {
Thread.sleep(1000); // 毫秒
Thread.sleep(1000, 500000); // 毫秒 + 奈秒
} catch (InterruptedException e) {
Thread.currentThread().interrupt(); // 重設中斷旗標
}
yield() - 讓出 CPU、不保證調度
Thread.yield(); // 暗示調度器:我暫時不搶 CPU,同優先級其他執行緒可執行
join() - 等待其他執行緒結束
Thread t = new Thread(() -> { ... });
t.start();
t.join(); // 無限等待
t.join(5000); // 最多等 5 秒
interrupt() - 中斷執行緒
// 中斷沉睡/等待中的執行緒
Thread t = new Thread(() -> {
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
System.out.println("被中斷了");
Thread.currentThread().interrupt(); // 重要:重設旗標
}
});
t.start();
Thread.sleep(100);
t.interrupt(); // 觸發 InterruptedException
同步化
synchronized 關鍵字
// 1. 同步實例方法(鎖住 this)
public synchronized void deposit(int amount) {
balance += amount;
}
// 2. 同步靜態方法(鎖住 Class 物件)
public static synchronized void staticMethod() { ... }
// 3. 同步區塊(指定鎖物件,粒度更細)
public void transfer(Account from, Account to, int amount) {
synchronized (from) {
synchronized (to) {
// 轉帳邏輯
}
}
}
內建鎖特性
- 可重入:同一執行緒可重複獲得同一鎖
- 互斥:同一時間只有一個執行緒持有
- 釋放:正常返回或拋出例外時自動釋放
wait()/notify()/notifyAll() - 執行緒協作
必須在 synchronized 區塊/方法中呼叫,否則拋 IllegalMonitorStateException。
經典生產者-消費者模式
public class BoundedBuffer {
private final List buffer = new ArrayList<>();
private final int capacity;
public BoundedBuffer(int capacity) { this.capacity = capacity; }
public synchronized void put(T item) throws InterruptedException {
while (buffer.size() == capacity) {
wait(); // 釋放鎖並等待,被 notify 喚醒後重新檢查條件
}
buffer.add(item);
notifyAll(); // 喚醒所有等待執行緒(消費者)
}
public synchronized T take() throws InterruptedException {
while (buffer.isEmpty()) {
wait();
}
T item = buffer.remove(0);
notifyAll(); // 喚醒生產者
return item;
}
}
關鍵模式
- while 迴圈檢查條件:防止虛假喚醒
- notifyAll 優於 notify:避免喚醒錯誤類型執行緒導致卡死
- 狀態改變後通知:修改共享狀態 → notifyAll
死結與預防
死結條件(四必要條件同時滿足)
- 互斥:資源獨佔
- 持有並等待:持有資源同時請求新資源
- 不可搶佔:資源只能自願釋放
- 循環等待:T1 等 T2 持有的資源,T2 等 T1 持有的資源
死結範例
Object lockA = new Object();
Object lockB = new Object();
// Thread 1
synchronized (lockA) {
Thread.sleep(100);
synchronized (lockB) { ... } // 死結!
}
// Thread 2
synchronized (lockB) {
Thread.sleep(100);
synchronized (lockA) { ... } // 死結!
}
預防策略
- 固定鎖順序:所有執行緒按相同順序獲取鎖(如 hashCode 排序)
- 使用 tryLock(timeout):
ReentrantLock 支援限時獲取,失敗可釋放已持有鎖重試
- 縮小同步範圍:只在必要程式碼加鎖
- 避免巢狀鎖:重構設計減少多重鎖需求
daemon 執行緒(守護執行緒)
Thread t = new Thread(() -> { ... });
t.setDaemon(true); // 必須在 start() 前呼叫
t.start();
- JVM 結束條件:所有非 daemon 執行緒結束
- daemon 執行緒適合:背景清理、監控、定時任務
- 注意:daemon 執行緒在 JVM 關閉時會被強制終止,不執行 finally 區塊
Java 5+ 高階 API 快速導覽(實務首選)
| 類別/介面 | 用途 |
| ExecutorService | 執行緒池管理,替代手動 new Thread() |
| Future / CompletableFuture | 非同步運算結果 |
| CountDownLatch | 等待多個任務完成 |
| CyclicBarrier | 多執行緒同步屏障 |
| Semaphore | 限制同時存取數量 |
| ConcurrentHashMap | 執行緒安全 Map |
| CopyOnWriteArrayList | 讀多寫少安全 List |
| BlockingQueue | 生產者-消費者佇列 |
| ReentrantLock / ReadWriteLock | 進階鎖功能 |
| ThreadLocal | 執行緒區域變數 |
總結
- 優先用
Runnable + ExecutorService,少用繼承 Thread
- 理解六大狀態與轉換,善用
join/interrupt 控制流程
synchronized 是基礎,掌握 wait/notifyAll 協作模式(while 迴圈 + notifyAll)
- 死結預防:固定鎖順序、tryLock、縮小同步範圍
- 實務專案直接使用 java.util.concurrent,別重造輪子
- 上一篇文章: Java 新手入門指南:JDK 環境建置、CLASSPATH 設定、打包執行
- 下一篇文章: Java 內部類全解析(二):靜態嵌套類、成員內部類、局部類與匿名類的定義規則、訪問權限與實例化方式
Copyright ©
程式碼之家 All Rights Reserved