一、計算機系統與 JMM 基石
硬體層挑戰
- 高速快取:CPU 從主存複製熱點數據到 L1/L2/L3 快取,運算後再回寫,解決 CPU 與主存速度差
- 快取一致性協議(MESI 等):多核心下保證各核心快取數據一致
- 亂序執行優化:CPU 為榨乾吞吐率,可能重排指令順序
Java 內存模型 (JMM) 核心規範
- 所有共享變量存儲於主內存
- 每條線程有私有工作內存,持有共享變量副本
- 線程操作變量必須在工作內存,不可直接讀寫主內存
- 線程間通信必須經過主內存中轉
八大內存交互操作
| 操作 | 作用對象 | 含義 |
| lock | 主內存 | 標識變量為線程獨佔 |
| read | 主內存 | 傳輸變量值到工作內存 |
| load | 工作內存 | 將 read 獲取的值放入副本 |
| use | 工作內存 | 傳遞值給執行引擎 |
| assign | 工作內存 | 執行引擎賦值給副本 |
| store | 工作內存 | 傳送副本值到主內存 |
| write | 主內存 | 將 store 獲取的值寫入主內存 |
| unlock | 主內存 | 釋放獨佔狀態 |
關鍵規則
- read/load、store/write 必須成對出現
- assign 後必須同步回主內存
- 新變量只能在主內存誕生
- 同一時刻僅一線程可 lock,但可重入
- lock 會清空工作內存副本,需重新 read/load
- unlock 前必須同步回主內存
二、volatile、原子性、可見性、有序性
volatile 兩大語義
- 可見性:寫入後立即刷新到主內存,讀取前強制從主內存載入
- 禁止指令重排序:寫屏障 + 讀屏障,保證前後操作不越界
適用場景:單寫多讀、狀態標誌、DCL 雙重檢查鎖單例模式。
三大特性對比
| 特性 | 實現手段 | 備註 |
| 原子性 | 基本類型讀寫、synchronized、Lock、原子類 | 長整型/雙精度在 32 位 JVM 非原子 |
| 可見性 | volatile、synchronized (unlock 前 flush)、final | final 構造器初始化完成且 this 不逸出 |
| 有序性 | volatile (禁止重排)、synchronized (鎖串行化) | happens-before 原則是核心判斷依據 |
三、Happens-Before 先行發生原則
判斷操作 A 是否對操作 B 可見的偏序關係,八大規則:
- 程序次序:單線程內按控制流順序
- 管程鎖定:unlock 先於後續同鎖 lock
- volatile 變量:寫先於後續讀
- 線程啟動:start() 先於線程內任意操作
- 線程終止:線程內操作先於 join()/isAlive() 檢測
- 線程中斷:interrupt() 先於中斷檢測
- 對象終結:構造器結束先於 finalize()
- 傳遞性:A hb B, B hb C ⇒ A hb C
時間先後順序 ≠ Happens-Before;HB 是編譯器/運行時的承諾,而非物理時鐘。
四、線程實現模型與調度
三種實現模型
- 1:1 內核線程:Java 線程直接映射 LWP(Linux/Windows JDK 默認),調度交給 OS,阻塞一個不影響其他
- N:1 用戶線程:完全用戶態管理,切換快但無法利用多核,一個阻塞全阻塞
- M:N 混合模型:用戶線程多對多映射內核線程,複雜度高
線程調度
- Java 採用搶占式:時間片由 OS 分配,線程不可控制切換
- 優先級(1-10)僅為提示,平台相關性強,不可過度依賴
五、線程六大狀態
| 狀態 | 觸發條件 | 備註 |
| NEW | new Thread() | 未 start |
| RUNNABLE | start() | 包含 Ready + Running |
| BLOCKED | 等待 synchronized 鎖 | 等待進入同步塊/方法 |
| WAITING | wait()、join()、LockSupport.park() | 無超時,需顯式喚醒 |
| TIMED_WAITING | sleep、wait(timeout)、join(timeout) | 超時自動返回 |
| TERMINATED | run() 結束 | 正常或異常結束 |
六、線程安全分級與實現策略
安全分級
- 不可變:final 基本類型、String、enum、Number 子類(除 Atomic)
- 絕對線程安全:極少,如 CopyOnWriteArrayList
- 相對線程安全:單操作安全,複合操作需外部同步(Vector、HashTable)
- 線程兼容:需調用方同步
- 線程對立:無論是否同步都不安全(System.setIn/Out)
三大實現路線
- 互斥同步:synchronized(JVM 內建、可重入、自動釋放)、ReentrantLock(可中斷、公平鎖、多 Condition)
- 非阻塞同步 (CAS):樂觀鎖,AtomicInteger 等,自旋重試直到成功
- 無同步方案:可重入代碼(無狀態)、ThreadLocal(線程封閉)、棧封閉
七、JVM 鎖優化演進
Mark Word 狀態機 (32 位 HotSpot)
| 狀態 | Mark Word 內容 | 標誌位 |
| 無鎖 | HashCode、Age | 01 |
| 輕量級鎖 | 指向棧幀 Lock Record 指針 | 00 |
| 重量級鎖 | 指向 ObjectMonitor 指針 | 10 |
| GC 標記 | 空 | 11 |
| 可偏向 | ThreadID、Epoch、Age | 01 |
四階段升級
- 偏向鎖:無競爭時,CAS 寫入 ThreadID,後續進出同步塊零開銷
- 輕量級鎖:競爭出現,CAS 將 Mark Word 指向棧幀 Lock Record,自旋等待
- 膨脹重量級鎖:自旋失敗/多線程競爭,轉用 OS Mutex,線程阻塞
- 鎖消除/粗化:JIT 編譯期優化,逃逸分析判斷無競爭則消除,連續同步塊合併
八、核心 API 精要
| 方法 | 關鍵語義 | 鎖行為 |
| wait() | 釋放鎖、進入等待隊列 | 釋放當前對象鎖 |
| notify()/notifyAll() | 喚醒等待隊列線程 | 需持有鎖,不釋放鎖 |
| sleep() | 讓出 CPU、不釋放鎖 | 保持所有鎖 |
| join() | 等待目標線程終止 | 底層用 wait() 實現 |
| yield() | 提示調度器重新調度 | 不保證讓出 |
| interrupt() | 設置中斷標誌、喚醒阻塞 | 被阻塞時拋 InterruptedException 並清除標誌 |
九、死鎖與避免
- 條件:互斥、請求保持、不可剝奪、循環等待
- 常見:雙線程互相 join、嵌套同步塊加鎖順序不一致
- 預防:固定加鎖順序、tryLock 超時、避免長事務、使用並發容器替代手工同步