首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 綜合教學

Java 並發編程精進:不覆寫 start、停止線程正確姿勢、優先級三級制與線程池核心原理

     編輯:Java 綜合教學

前言

《編寫高質量代碼:改善 Java 程序的 151 個建議》第 8 章建議 118~122,聚焦線程生命週期控制與線程池本質。本文深度解析:為什麼不覆寫 start()、stop() 為何不可靠、正確停止線程模式、線程優先級三級制、線程池 Worker-Queue-Task 三角機制。

建議 118:不覆寫 start() 方法

錯誤示範


class MyThread extends Thread {
    @Override public synchronized void start() { run(); } // 錯誤:直接調用 run()
    @Override public void run() { /* 業務 */ }
}

後果:main 調用 start() 實際在主線程同步執行 run(),**未創建新線程**,整個程序只有主線程。

源碼級原理 (JDK Thread.start)


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() 並放在首行/尾行,但實戰無必要場景,事件/監聽/回調可替代。

建議 119:啟動前調用 stop() 不可靠

場景


while (true) {
    Thread t = new MyThread();
    if (!condition) t.stop(); // 試圖標記「不可啟動」
    t.start(); // 部分線程仍會啟動!
}

源碼揭秘 (JDK 1.6 Thread.stop/start)

  • stop() 針對 NEW 狀態線程:設置 stopBeforeStart = true、保存 throwableFromStop;
  • start() 內部順序:start0() → if (stopBeforeStart) stop0(throwableFromStop);
  • 缺陷:start0() (創建線程、運行 run) **先於** 停止邏輯執行!即「先跑起來,再殺掉」。

修正


if (condition) new MyThread().start(); // 直接判斷決定是否創建

建議 120:不使用 stop() 停止運行中線程

三大罪狀

  1. 已過時:@Deprecated,語言層面棄用;
  2. 破壞邏輯完整性:強制拋出 ThreadDeath Error,run() 中斷,finally 雖執行但業務邏輯 (如資源釋放、狀態持久化) 可能未完成;
  3. 破壞原子性/鎖一致性:stop() 釋放線程持有的所有監視器鎖,導致被保護的不變性條件暴露,產生髒數據。

經典演示:synchronized 保護的 a++/a-- 被 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 (原子性破壞)

正確停止模式:volatile 標誌位 / interrupt()


// 模式 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(),安全可控。

建議 121:線程優先級只用三個等級

實測現象

創建 20 個優先級 1~10 的線程,各執行 CPU 密集任務,觀察完成順序:
非嚴格按優先級 (優先級 5 可能先於 7);
優先級差距大 (10 vs 1) 趨勢明顯,差距小 (6 vs 5) 順序隨機。

根因:OS 優先級映射差異

OS優先級數量備註
Windows7 級含實時、高、正常、低、閒置等
Linux140 級 (nice -20~19 + 實時)nice 值越低優先級越高
FreeBSD255 級
Java10 級 (1~10)跨平台抽象,映射到 OS 對應級別

Java 10 級優先級映射到 OS 不同級別,**語義僅為「搶佔 CPU 機率大小」**,非強制調度順序。

Thread 內置三常量


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)。

建議 122 回顧:線程池核心原理 (Worker + Queue + Task)

三大核心組件

  • Worker:常駐線程,運行 run() 循環:getTask() → runTask(),狀態僅 RUNNABLE/WAITING;
  • Task:Runnable (無返回值) / Callable (有返回值/異常);
  • WorkQueue:BlockingQueue (如 LinkedBlockingQueue),生產者-消費者緩衝區。

線程複用關鍵:Worker.run() 循環


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() 實現阻塞喚醒。

線程池創建流程

  1. Executors.newFixedThreadPool(2) → ThreadPoolExecutor(2,2,0,ms, new LinkedBlockingQueue());
  2. 首次 submit() → execute() → 創建 Worker (不超過 corePoolSize) → 啟動 → 從隊列取任務;
  3. 後續任務入隊,空閒 Worker take() 獲取執行,**線程對象不銷毀,實現複用**;
  4. 隊列為 0 時 Worker 阻塞等待,新任務 signal() 喚醒。

總結

線程控制鐵律:
🚫 不覆寫 start(),保留 JVM 線程創建權;
🚫 不調用 stop(),無論啟動前還是運行中;
✅ volatile 標誌 / interrupt() 協作式停止;
✅ 優先級只用 MIN/NORM/MAX,拒絕中間值;
✅ 生產環境必上線程池,理解 Worker-Queue-Task 循環,合理配置核心數、隊列類型、拒絕策略、線程工廠、監控指標。
掌握這五點,並發編程穩健性提升 90%。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved