《編寫高質量代碼:改善 Java 程序的 151 個建議》第 8 章建議 118~122,聚焦線程生命週期控制與線程池本質。本文深度解析:為什麼不覆寫 start()、stop() 為何不可靠、正確停止線程模式、線程優先級三級制、線程池 Worker-Queue-Task 三角機制。
class MyThread extends Thread {
@Override public synchronized void start() { run(); } // 錯誤:直接調用 run()
@Override public void run() { /* 業務 */ }
}
後果:main 調用 start() 實際在主線程同步執行 run(),**未創建新線程**,整個程序只有主線程。
public synchronized void start() {
if (threadStatus != 0) throw new IllegalThreadStateException(); // 狀態檢查
group.add(this); // 加入線程組
boolean started = false;
try {
start0(); // ★ 本地方法:分配棧內存、創建 OS 線程、調用 run()
started = true;
} finally {
if (!started) group.threadStartFailed(this);
}
}
private native void start0(); // JVM 核心:線程創建與棧分配
結論:start0() 是線程誕生的唯一入口,覆寫 start() 切斷了 JVM 線程管理能力。若確需擴展,調用 super.start() 並放在首行/尾行,但實戰無必要場景,事件/監聽/回調可替代。
while (true) {
Thread t = new MyThread();
if (!condition) t.stop(); // 試圖標記「不可啟動」
t.start(); // 部分線程仍會啟動!
}
stop() 針對 NEW 狀態線程:設置 stopBeforeStart = true、保存 throwableFromStop;start() 內部順序:start0() → if (stopBeforeStart) stop0(throwableFromStop);start0() (創建線程、運行 run) **先於** 停止邏輯執行!即「先跑起來,再殺掉」。
if (condition) new MyThread().start(); // 直接判斷決定是否創建
ThreadDeath Error,run() 中斷,finally 雖執行但業務邏輯 (如資源釋放、狀態持久化) 可能未完成;stop() 釋放線程持有的所有監視器鎖,導致被保護的不變性條件暴露,產生髒數據。
class Task implements Runnable {
int a = 0;
public void run() {
synchronized("") { a++; sleep(100); a--; print(a); } // 期望恆為 0
}
}
Thread t1 = new Thread(new Task()); t1.start();
new Thread(new Task()).start(); // 其他線程
t1.stop(); // 導致其他線程輸出 a=1 (原子性破壞)
// 模式 1:volatile boolean (推薦簡單場景)
class SafeThread extends Thread {
private volatile boolean stop = false;
public void run() { while (!stop) { /* work */ } }
public void shutdown() { stop = true; }
}
// 模式 2:interrupt() (標準庫協作)
class SafeThread extends Thread {
public void run() { while (!Thread.interrupted()) { /* work */ } }
}
// 主線程調用 thread.interrupt(),配合阻塞方法 (sleep/wait/join/IO) 拋 InterruptedException 實現響應
線程池關閉:executor.shutdown() (拒絕新任務、處理完隊列) / shutdownNow() (嘗試中斷執行中任務),內部使用 interrupt(),安全可控。
創建 20 個優先級 1~10 的線程,各執行 CPU 密集任務,觀察完成順序:
非嚴格按優先級 (優先級 5 可能先於 7);
優先級差距大 (10 vs 1) 趨勢明顯,差距小 (6 vs 5) 順序隨機。
| OS | 優先級數量 | 備註 |
|---|---|---|
| Windows | 7 級 | 含實時、高、正常、低、閒置等 |
| Linux | 140 級 (nice -20~19 + 實時) | nice 值越低優先級越高 |
| FreeBSD | 255 級 | |
| Java | 10 級 (1~10) | 跨平台抽象,映射到 OS 對應級別 |
Java 10 級優先級映射到 OS 不同級別,**語義僅為「搶佔 CPU 機率大小」**,非強制調度順序。
public final static int MIN_PRIORITY = 1;
public final static int NORM_PRIORITY = 5; // 默認
public final static int MAX_PRIORITY = 10;
最佳實踐:只使用 MIN/NORM/MAX 三級,避免 2~9 七個中間值。跨平台行為最穩定,代碼意圖最清晰 (後台任務 MIN、普通 NORM、關鍵實時 MAX)。
run() 循環:getTask() → runTask(),狀態僅 RUNNABLE/WAITING;Runnable (無返回值) / Callable (有返回值/異常);BlockingQueue (如 LinkedBlockingQueue),生產者-消費者緩衝區。
public void run() {
try {
Runnable task = firstTask;
firstTask = null;
while (task != null || (task = getTask()) != null) {
runTask(task); // 執行用戶任務
task = null;
}
} finally {
workerDone(this);
}
}
Runnable getTask() {
for (;;) {
return workQueue.take(); // 隊列空則阻塞,有任務則喚醒
}
}
LinkedBlockingQueue.take() 內部:ReentrantLock + Condition.await()/signal() 實現阻塞喚醒。
Executors.newFixedThreadPool(2) → ThreadPoolExecutor(2,2,0,ms, new LinkedBlockingQueue());submit() → execute() → 創建 Worker (不超過 corePoolSize) → 啟動 → 從隊列取任務;take() 獲取執行,**線程對象不銷毀,實現複用**;signal() 喚醒。線程控制鐵律:
🚫 不覆寫 start(),保留 JVM 線程創建權;
🚫 不調用 stop(),無論啟動前還是運行中;
✅ volatile 標誌 / interrupt() 協作式停止;
✅ 優先級只用 MIN/NORM/MAX,拒絕中間值;
✅ 生產環境必上線程池,理解 Worker-Queue-Task 循環,合理配置核心數、隊列類型、拒絕策略、線程工廠、監控指標。
掌握這五點,並發編程穩健性提升 90%。