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

Java 並發編程進階:線程異常處理、volatile 陷阱、Callable 與線程池最佳實踐

     編輯:Java 綜合教學

前言

《編寫高質量代碼:改善 Java 程序的 151 個建議》第 8 章聚焦多線程與並發。本文精選建議 122~125,從線程異常自愈、volatile 同步誤區、異步計算接口選型、線程池核心原理四個維度,為高併發系統設計提供落地指導。

建議 122:使用線程異常處理器提升系統可靠性——讓關鍵服務「自動重啟」

場景痛點

Socket 類長連接服務(如即時通訊、數據同步)常面臨兩大挑戰:避免線程阻塞、保證服務不中斷。一旦守候線程因不可預期異常退出,服務將不可用。

解決方案:Thread.setUncaughtExceptionHandler

Java 1.5+ 提供線程級異常捕獲機制。示例模式:

class TcpServer implements Runnable {
    public TcpServer() {
        Thread t = new Thread(this);
        t.setUncaughtExceptionHandler(new TcpServerExceptionHandler());
        t.start();
    }
    public void run() { /* 業務邏輯 */ throw new RuntimeException(); }
    static class TcpServerExceptionHandler implements Thread.UncaughtExceptionHandler {
        public void uncaughtException(Thread t, Throwable e) {
            // 1. 記錄異常堆棧,便於事後分析
            // 2. 重啟新線程,保證服務不中斷
            new TcpServer();
        }
    }
}

生產環境三大避坑指南

  • 共享資源鎖死:若異常源於資源鎖定(如寫入阻塞),盲目重啟只會加劇爭用。需檢測鎖狀態,必要時停機釋放資源。
  • 髒數據導致邏輯不一致:異常中斷原子操作(如用戶認證與簽到需事務性),重啟後可能殘留中間狀態。需引入冪等設計或狀態機校驗。
  • 內存洩漏風險:舊線程對象未及時回收,頻繁重啟創建大量對象(特別是場景接管類服務)易導致 OOM。需控制重啟頻率、引入緩衝機制。

建議 123:volatile 不能保證數據同步——可見性 ≠ 原子性

原理回顧

volatile 強制線程直接讀寫主內存,繞過工作內存(寄存器/Cache),保證可見性:線程 A 寫入後,線程 B 能立即讀到最新值。

經典反例:volatile int count; count++ 仍不安全

count++ 非原子操作(讀取 → 加一 → 寫入)。多線程併發時,即使讀到最新值,也會發生「丟失更新」:
線程 A 讀 998 → 線程 B 讀 998 → A 寫 999 → B 寫 999 → 結果 999(期望 1000)。

驗證代碼(啟動 1000 線程並發自增,循環檢測)可穩定復現線程不安全。

結論

volatile 適用於單寫多讀、狀態標誌、雙重檢查鎖等場景;複合操作必須配合 synchronized、Lock 或 AtomicInteger 實現原子性。

建議 124:異步運算優先選擇 Callable 接口——獲取返回值與異常的標準化路徑

Runnable 缺陷

run() 無返回值、不可拋檢查異常,獲取結果需自行設計共享變量或回調,擴展性差。

Callable + Future 模式

// 任務定義
class TaxCalculator implements Callable {
    public Integer call() throws Exception {
        TimeUnit.SECONDS.sleep(2); // 模擬耗時計算
        return seedMoney / 10;
    }
}

// 執行與進度監控
ExecutorService es = Executors.newSingleThreadExecutor();
Future future = es.submit(new TaxCalculator(100));
while (!future.isDone()) {
    System.out.print("*"); // 進度提示
    TimeUnit.MILLISECONDS.sleep(200);
}
System.out.println("稅金:" + future.get() + " 元");
es.shutdown();

優勢

  1. 異步非阻塞,主線程可做進度展示、超時控制、並行編排;
  2. Future 統一封裝「結果、異常、完成狀態」,異常可通過 future.get() 重拋捕獲;
  3. 配合 ExecutorService 實現線程生命週期管理。

建議 125:優先選擇線程池——複用線程,消除創建銷毀開銷

線程不可重啟的本質

Java 線程狀態機:NEW → RUNNABLE → BLOCKED/WAITING → TERMINATED。TERMINATED 無法轉回 RUNNABLE,start() 二次調用拋 IllegalThreadStateException。

線程池核心三要素

  • Worker(工作線程):池中常駐線程,僅兩狀態(RUNNABLE/WAITING),循環從隊列取任務執行;
  • Task(任務接口):Runnable(無返回值)或 Callable(有返回值/異常);
  • WorkQueue(任務隊列):通常為 BlockingQueue,實現生產者-消費者解耦。

核心流程(以 ThreadPoolExecutor 為例)

  1. submit() → 包裝為 RunnableFuture → execute();
  2. execute():創建 Worker(不超過核心數)→ 任務入隊 → Worker 從隊列 take() 獲取任務;
  3. Worker run() 循環:runTask() 執行用戶任務 → getTask() 阻塞取下一任務;
  4. 隊列為空時 Worker await() 等待,新任務入隊 signal() 喚醒。

實戰價值

Servlet 容器(Tomcat、Jetty)均採用線程池處理請求。若無池化,每請求創建銷毀線程將導致系統在高併發下 CPU 飆升、響應延遲、甚至 OOM。線程池將 T1(啟動) + T3(銷毀) 歸零,僅保留 T2(業務執行),是高併發系統的基礎設施。

總結

並發編程無銀彈,但有原則:
✅ 關鍵服務加異常處理器實現自愈,但要警惕資源鎖、髒數據、內存洩漏;
✅ volatile 只負責可見性,複合操作上鎖;
✅ 異步有結果用 Callable,無結果用 Runnable,統一交給線程池調度;
✅ 線程池是生產環境標配,理解 Worker-Queue-Task 三角關係,合理配置核心數、隊列類型、拒絕策略。
掌握這四點,已解決 80% 以上的並發工程難題。

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