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

Java 並發編程核心精講:從線程基礎到 JMM、鎖、死鎖與線程池

     編輯:Java 綜合教學

Java 並發編程核心精講:從線程基礎到 JMM、鎖、死鎖與線程池

並發編程是利用多核 CPU 提升吞吐量的關鍵,也是 Java 工程師進階必經之路。本文系統梳理:線程優勢與代價、創建啟動方式、競態條件與臨界區、線程安全與逃逸分析、不可變模式、Java 記憶體模型 (JMM)、synchronized 鎖優化、線程通信、死鎖預防與診斷,並附帶最佳實踐清單。

一、為什麼需要多線程?

優勢說明
資源利用率更高I/O 等待時 CPU 可切換執行其他線程(如讀磁盤 5ms + 處理 2ms → 並行讀取兩文件總耗時 12ms vs 串行 14ms)
建模更簡單每個任務一個線程,邏輯獨立,避免手動狀態機管理
響應更快主線程分派任務給工作者線程,立即返回處理新請求(如 Web 服務器、GUI 應用)

二、代價與風險

  • 設計複雜度:共享數據同步、死鎖、活鎖、飢餓。
  • 上下文切換開銷:保存/恢復寄存器、程式計數器、棧幀(~微秒級)。
  • 記憶體佔用:每線程 ~1MB 棧空間,大量線程導致 OOM。

三、線程創建與啟動

方式一:繼承 Thread


class MyThread extends Thread {
    @Override public void run() { System.out.println("Running: " + getName()); }
}
new MyThread().start(); // 必須呼叫 start(),而非 run()

方式二:實作 Runnable(推薦)


class MyRunnable implements Runnable {
    @Override public void run() { System.out.println("Running"); }
}
new Thread(new MyRunnable()).start();

優勢:分離任務邏輯與執行機制,易於提交給線程池、實現多介面。

常見錯誤


new Thread(new MyRunnable()).run(); // 錯誤:在當前線程同步執行 run(),未啟動新線程

線程命名與獲取


Thread t = new Thread(runnable, "Worker-1");
t.start();
String name = Thread.currentThread().getName(); // 當前線程名

四、競態條件、臨界區與線程安全

核心定義

  • 共享資源:堆上物件、靜態變數、文件、資料庫連線。
  • 競態條件:多線程讀寫共享資源,結果依賴執行時序。
  • 臨界區:訪問共享資源的代碼片段。
  • 線程安全:無競態條件,多線程正確執行無需外部同步。

經典非線程安全計數器


class Counter { long count = 0;
    void add(long v) { count = count + v; } // 非原子:讀-改-寫三步
}
// 兩線程並發 add(2)、add(3) 可能結果 3 或 5,而非 5

五、線程安全的三大支柱

支柱核心原則典型實現
封閉性資源不逃逸出線程局部變數、ThreadLocal、棧封閉
不可變性構造後狀態不變final 欄位、無 setter、防御性複製、String、包裝類
同步機制協調訪問順序synchronized、Lock、volatile、原子類、並發集合

逃逸分析規則

若資源的「創建、使用、銷毀」全在同一線程內完成,且永不脫離該線程控制,則該資源使用是線程安全的。

注意:物件線程安全 ≠ 物件引用線程安全。持有不可變物件的可變引用(如 volatile Immutable ref)仍需同步。

不可變模式範例


final class ImmutableValue {
    private final int value;
    public ImmutableValue(int v) { this.value = v; }
    public int getValue() { return value; }
    public ImmutableValue add(int v) { return new ImmutableValue(value + v); } // 返回新實例
}

六、Java 記憶體模型 (JMM)

抽象結構

  • 線程棧:每線程私有,存放局部變數(基本類型、引用)、方法幀。不共享。
  • 堆:所有線程共享,存放物件實例、陣列、靜態欄位。

JMM 邏輯視圖 棧與堆交互

關鍵語義

  • 可見性:一線程寫入,另一線程能看到。volatile、synchronized、final 保證。
  • 有序性:禁止指令重排序破壞語義。happens-before 規則定義偏序關係。
  • 原子性:操作不可分割。基本類型讀寫(long/double 除外)原子;複合操作需鎖/CAS。

七、synchronized 鎖機制

四種同步塊

類型監視器對象鎖粒度
實例方法 synchronized void m()this實例級
靜態方法 static synchronized void m()Class 物件類級
實例方法塊 synchronized(this) {}this塊級(可縮小範圍)
靜態方法塊 synchronized(MyClass.class) {}Class 物件塊級

鎖升級與優化 (JDK 6+)

  1. 偏向鎖:無競爭時,鎖偏向首個獲得線程,後續進出無 CAS 開銷。
  2. 輕量級鎖:少量競爭,用 CAS 替換監視器對象 Mark Word,自旋等待。
  3. 重量級鎖:激烈競爭,膨脹為 ObjectMonitor,阻塞等待(OS 互斥量)。
  4. 鎖消除:JIT 編譯器發現無共享競爭,直接刪除同步塊。
  5. 鎖粗化:連續對同一鎖操作,合併為一大塊減少進出開銷。

八、線程通信:wait/notify 與 Condition


// 傳統 Object 監視器
synchronized (lock) {
    while (!condition) lock.wait(); // 釋放鎖並阻塞
    // 條件滿足執行
    lock.notifyAll(); // 喚醒所有等待線程
}

// Lock + Condition(更靈活,支援多條件隊列)
Lock lock = new ReentrantLock();
Condition notEmpty = lock.newCondition();
lock.lock();
try {
    while (queue.isEmpty()) notEmpty.await();
    take();
    notFull.signal();
} finally { lock.unlock(); }

九、死鎖:成因、預防與診斷

四個必要條件(科夫曼條件)

  1. 互斥:資源獨佔。
  2. 持有並等待:持有鎖申請新鎖。
  3. 不可搶佔:鎖只能主動釋放。
  4. 循環等待:線程環狀等待鎖。

打破任一條件即可預防死鎖。

三大預防策略

  1. 固定加鎖順序:所有線程按相同順序獲取鎖(如先 A 再 B)。
  2. 超時放棄:lock.tryLock(timeout),失敗釋放已持有鎖重試。
  3. 死鎖檢測:ThreadMXBean.findDeadlockedThreads() 定期掃描,配合 jstack 分析。

jstack 診斷實戰


jstack  > thread_dump.txt
# 搜尋 "Found one Java-level deadlock" 或 "BLOCKED on ... owned by"

十、最佳實踐清單

  1. 優先用不可變物件、線程封閉避免同步。
  2. 同步塊盡可能小,避免在同步塊內調用外部方法/IO。
  3. 用並發容器(ConcurrentHashMap、CopyOnWriteArrayList、BlockingQueue)替代手動同步。
  4. 用線程池(ExecutorService)管理線程生命週期,拒絕無限制創建。
  5. 用CompletableFuture、並行流 簡化非阻塞協作。
  6. 用volatile 修飾單一狀態標誌(如關閉標誌),複合操作仍需鎖。
  7. 定期用 jstack、VisualVM、Arthas 排查阻塞、死鎖、CPU 熱點。

並發編程的本質是在共享資源訪問上協調時序。掌握 JMM 語義、鎖機制、線程通信模式與死鎖預防,配合現代高階 API(並發包、CompletableFuture、虛擬線程),即可構建高吞吐、低延遲、穩定可靠的 Java 系統。

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