預告下一本 可能是mysql8+的書籍 或者是AI應用工程的基本崗位所有技能
問題1
什么是 線程安全?在 Java 中如何定義“線程安全”?
線程安全(Thread Safety) 的定義是:
- 當多個線程同時訪問某個類的對象時,無論運行環境如何調度這些線程、無論這些線程如何交替執行,并且在主調代碼中不需要任何額外的同步或協調操作,這個類都能表現出正確的行為。
在 Java 中,線程安全意味著:
- 對外暴露的方法調用都是 原子性 的。
- 并發訪問時不會導致 數據不一致、狀態破壞 或 異常行為。
- 典型例子:
java.util.Vector
、Hashtable
、ConcurrentHashMap
等線程安全類。
👉 換句話說:線程安全的本質 = 行為一致性,不隨并發環境變化而變化。
問題2
作者將“線程安全”劃分為不同的 粒度層次。請問有哪些分類?并簡要說明它們的區別。
你提到的“無鎖、偏向鎖、輕量級鎖、重量級鎖” 👍,這其實是 JVM 在實現 synchronized 時的鎖狀態劃分和優化路徑(屬于 鎖優化 的部分)。
線程安全的分類(語義層次)
作者把“線程安全”從強到弱分為以下幾類:
-
不可變(Immutable)
- 對象一旦創建,其狀態就不會改變。
- 典型例子:
String
、Integer
、Long
等包裝類。 - 天然線程安全。
-
絕對線程安全
- 滿足嚴格定義:類在 任意調用場景下 都是線程安全的。
- 需要在所有方法調用上都進行同步。
- 代價極高,Java 標準庫幾乎沒有完全絕對線程安全的類。
-
相對線程安全(常見的“線程安全”定義)
- Java 大多數線程安全類遵循這一標準。
- 在特定條件下能保證正確行為,比如
Vector
、Hashtable
、Collections.synchronizedXXX()
。 - 通常指 單個方法是原子安全的,但復合調用(如“先檢查再更新”)仍需額外同步。
-
線程兼容(Thread-Compatible)
- 類本身不是線程安全的,但只要 調用方在外部進行正確同步,就能在多線程環境中安全使用。
- 典型例子:
ArrayList
、HashMap
。
-
線程對立(Thread-Hostile)
- 類在并發環境下完全無法使用,即使調用方進行外部同步也無濟于事。
- 很少見,多出現在不規范的第三方代碼中。
問題3
互斥同步(Mutual Exclusion Synchronization)的基本原理是什么?在 Java 中有哪些典型實現?
-
基本原理
- 互斥同步的核心是:同一時刻只允許一個線程進入臨界區,訪問共享資源。
- 實現方式通常是:當一個線程獲取鎖標記后,其他線程必須 阻塞,直到鎖被釋放。
- 這種同步機制保證了 互斥性(Mutual Exclusion),從而避免數據競爭。
-
在 Java 中的典型實現
-
synchronized
- Java 內置關鍵字,編譯后會生成
monitorenter
/monitorexit
字節碼指令。 - 由 JVM 保證 原子性、可見性、有序性。
- Java 內置關鍵字,編譯后會生成
-
ReentrantLock
(顯示鎖)- 位于
java.util.concurrent.locks
包。 - 功能更豐富,比如可定時嘗試獲取鎖、公平/非公平策略、支持條件變量。
- 位于
-
其它并發工具
Semaphore
(信號量)、CountDownLatch
、CyclicBarrier
等,也都基于“互斥/同步”思想。
-
-
特點
- 優點:實現簡單,語義清晰,保證嚴格的互斥訪問。
- 缺點:可能導致線程 阻塞/喚醒切換開銷大,在高并發場景下性能不理想。
👉 總結:
互斥同步的本質是 “搶占標記 → 獨占訪問 → 釋放標記”,在 Java 中主要通過 synchronized
和 Lock
系列實現。
好的 👍,那我繼續自動拋出下一題。
問題4
非阻塞同步(基于 CAS)的基本原理是什么?它的優點和缺點是什么?
-
基本原理
-
CAS(Compare-And-Swap)是一種 非阻塞同步機制。
-
操作過程:
- 比較內存中的值是否與預期值一致;
- 如果一致,就更新為新值;
- 如果不一致,說明有競爭,操作失敗,通常會 自旋重試。
-
整個過程由 CPU 提供的原子指令保證,不會出現“更新一半被打斷”。
-
-
優點
- 無阻塞:線程不會因為獲取鎖失敗而掛起,而是立即返回/重試。
- 高性能:在競爭不激烈時,性能遠優于鎖(避免上下文切換)。
- 原子性保證:由硬件指令直接支持。
-
缺點
-
ABA 問題:值從 A 改為 B,再改回 A,CAS 會誤認為值沒變。
- 解決方法:加版本號(如
AtomicStampedReference
)。
- 解決方法:加版本號(如
-
自旋開銷大:在高并發或競爭激烈時,CAS 可能長時間重試,反而比鎖更耗性能。
-
只能保證單個變量的原子性:要實現復合操作(如多個共享變量的一致更新)需要額外機制。
-
👉 總結:
CAS 屬于 樂觀并發控制,適合“讀多寫少、競爭不激烈”的場景。它是 Java 并發包(AtomicInteger
、ConcurrentHashMap
)等的核心實現手段。
問題5
無同步方案 通常適用于哪類場景?請舉例說明。
無同步方案 的核心思想是:
- 當多個線程之間 不存在共享數據,或即使存在也 不需要嚴格同步 時,可以完全不使用任何同步手段。
典型適用場景:
-
可重入代碼(Reentrant Code)
- 又叫純代碼(Pure Code),方法內部沒有引用任何共享數據,輸出只依賴輸入參數。
- 例如數學函數:
Math.sin()
、Math.sqrt()
。
-
線程本地存儲(Thread Local Storage)
- 每個線程維護獨立的副本,互不影響。
- Java 提供了
ThreadLocal
工具類。
-
局部變量
- 定義在方法體中的局部變量存放在棧幀上,每個線程有獨立的棧,天然線程安全。
-
只讀共享數據
- 數據在初始化后不再改變,多個線程只讀而不寫。
- 比如配置常量、不可變對象(
String
、Integer
)。
👉 總結:
無同步方案適合 數據不會競爭 的情況,它讓代碼更高效,避免了不必要的鎖和 CAS 開銷。
問題6
Java 內置的哪些關鍵字和工具可以用來保證線程安全?請至少列出三種,并簡要說明作用。
synchronized
:內置監視器鎖,提供互斥 + 可見性 + 有序性(編譯為monitorenter/monitorexit
)。volatile
:提供可見性和禁止重排序(不保證復合操作原子性)。java.util.concurrent.locks.Lock
/ReentrantLock
:顯式鎖,支持可中斷/可定時/公平鎖、條件變量等高級特性。- 原子類(
AtomicInteger
等):基于 CAS 的非阻塞同步,保證單變量原子更新。 - 并發容器:
ConcurrentHashMap
、ConcurrentLinkedQueue
等,封裝了內部同步/無鎖算法。 - 同步器:
Semaphore
、CountDownLatch
、CyclicBarrier
、Phaser
、Exchanger
(多樣化協作/限流)。 - 讀寫鎖 / 票據鎖:
ReadWriteLock
、StampedLock
(樂觀/悲觀讀、寫)。 ThreadLocal
:線程本地存儲,繞過共享從而“無同步”。final
(發布語義):構造期的寫入有特殊的安全發布語義(配合 JMM)。
問題7
synchronized
的基本語義是什么?它在字節碼層面如何體現?(提示:兩個字節碼指令;保證哪三種內存語義?)
-
基本語義
-
synchronized
是 Java 內置的同步機制,具有 互斥性(排他性):同一時間只能有一個線程進入同步塊。 -
在 內存語義上,它保證:
- 原子性:同步塊內的操作不可被其他線程打斷。
- 可見性:進入同步塊前會清空工作內存,從主內存加載最新值;退出時會把更新刷新到主內存。
- 有序性:加鎖與解鎖之間的代碼不會被重排序。
-
-
字節碼層面實現
-
synchronized
在編譯后會在同步代碼塊前后插入兩條字節碼指令:monitorenter
:獲取對象的監視器鎖。monitorexit
:釋放監視器鎖。
-
JVM 在執行這些指令時,會觸發鎖優化機制(偏向鎖 → 輕量級鎖 → 重量級鎖)。
-
-
JVM 層面的優化
- 現代 HotSpot 會對 synchronized 做優化:偏向鎖、輕量級鎖、自旋鎖、鎖粗化、鎖消除等。
- 在底層實現上,確實會用到 CPU 的
LOCK
前綴指令 + 內存屏障,保證硬件層面的可見性與順序性。
👉 總結:
- 語義:互斥 + 原子性 + 可見性 + 有序性。
- 字節碼:
monitorenter
/monitorexit
。 - 底層:依賴 CPU 原子指令和內存屏障。
問題8
ReentrantLock 與 synchronized 相比,有哪些額外特性?
ReentrantLock 與 synchronized 的區別/額外特性:
-
相同點
- 都是 可重入鎖:同一個線程可以多次獲取同一把鎖,不會死鎖。
-
ReentrantLock 的額外特性:
- 可中斷鎖:可以調用
lockInterruptibly()
,在等待鎖時能響應中斷。 - 可定時鎖:調用
tryLock(long time, TimeUnit unit)
,在超時后自動放棄等待。 - 公平鎖 / 非公平鎖:可以指定是否采用 FIFO 獲取鎖,避免線程“餓死”。
- 多個條件變量:通過
newCondition()
可以為不同等待隊列設置不同的喚醒條件,比 synchronized 的wait/notify
更靈活。 - 顯式獲取/釋放:必須手動調用
lock()
和unlock()
,更靈活,但也更容易出錯。
- 可中斷鎖:可以調用
-
synchronized 的特點:
- JVM 內置支持,語法層面保證,簡單易用。
- 自動釋放鎖(代碼塊退出時自動解鎖,不易忘記)。
- 鎖優化:偏向鎖、輕量級鎖、自旋鎖、重量級鎖,升級路徑透明給開發者。
👉 總結:
- synchronized:簡單、語法級,自動釋放鎖。
- ReentrantLock:功能更強大,可中斷、可定時、公平鎖、多條件隊列,更靈活,但需手動釋放鎖。
問題9
什么是 自旋鎖(Spin Lock)?它解決了什么問題?
-
自旋鎖的定義
- 當線程嘗試獲取鎖失敗時,不是立即進入阻塞狀態,而是在 用戶態循環等待(自旋),不斷重試是否能獲取鎖。
- 如果在一定次數/時間內仍然失敗,就會 升級為重量級鎖(阻塞/排隊)。
-
它解決的問題
- 傳統阻塞鎖(重量級鎖)一旦獲取失敗,就會進入 內核態阻塞,喚醒時需要 用戶態 ? 內核態切換,開銷大。
- 如果鎖 只會短暫被持有,那么切換上下文的成本甚至比等鎖的時間還高。
- 自旋鎖通過“原地等待”,避免了短時間內的上下文切換開銷。
-
特點
- 優點:適合 鎖占用時間非常短 的場景(比如幾十納秒到幾微秒)。
- 缺點:會浪費 CPU,如果自旋時間過長,會導致性能下降。
- HotSpot 的改進:引入 適應性自旋(Adaptive Spinning),根據線程狀態和歷史鎖競爭情況動態決定是否自旋。
👉 總結:
自旋鎖 = 用 CPU 忙等換取避免阻塞/喚醒的開銷,是 synchronized 鎖優化的一部分。
問題10
什么是 鎖消除(Lock Elimination)?它的原理是什么?
-
定義
- 鎖消除(Lock Elimination)是 即時編譯器(JIT) 在運行時的一項優化。
- 如果代碼中出現了同步塊,但經過 逃逸分析 發現,這些對象不會被多個線程共享,也就不存在競爭,那么這些同步操作會被直接消除。
-
原理
- 逃逸分析用于判斷對象是否會逃出方法/線程。
- 若對象完全不會逃出線程作用域(即線程私有),那么它上的鎖就是無意義的,可以直接去掉。
-
示例
public String concat(String s1, String s2) {StringBuffer sb = new StringBuffer();sb.append(s1);sb.append(s2);return sb.toString(); }
StringBuffer
的方法帶有 synchronized,但這里 sb 是局部變量,絕對不會被多線程共享。- JIT 會 鎖消除,直接去掉 synchronized,提升性能。
-
效果
- 減少無用的加鎖/解鎖操作。
- 提升性能,尤其在高頻調用場景中。
👉 總結:
鎖消除 = 通過逃逸分析發現“無競爭鎖” → 刪除無意義的同步。
問題11
什么是 鎖粗化(Lock Coarsening)?它解決了什么問題?
-
定義
- 鎖粗化(Lock Coarsening) 是 JVM 的一種優化。
- 如果 JIT 發現一系列 連續的加鎖/解鎖操作作用在同一個對象上,就會把這些零碎的鎖合并成一個更大范圍的鎖。
-
為什么需要?
- 按照常規推薦,鎖粒度應盡量小,以減少鎖持有時間。
- 但如果多個同步塊連續執行,每次都要 頻繁加鎖/解鎖,反而帶來額外開銷。
- 這種情況下,粗化鎖的范圍可以減少反復申請和釋放鎖的成本。
-
示例
for (int i = 0; i < 100; i++) {synchronized (obj) {// 只有很小的操作sum++;} }
- 常規寫法是 100 次加鎖/解鎖。
- JIT 會將這 100 次鎖 粗化為一次大的加鎖/解鎖,把整個循環都包進一個鎖中。
-
效果
- 減少頻繁加鎖/解鎖的性能損耗。
- 犧牲部分并發性(鎖范圍變大),換取整體性能提升。
👉 總結:
- 常規建議:同步塊要小,減少持有時間。
- 鎖粗化:當多次鎖操作緊鄰時,JIT 會合并它們,減少頻繁申請/釋放鎖的成本。
問題12
偏向鎖、輕量級鎖、重量級鎖三者的區別是什么?它們的 升級路徑 是怎樣的?
在 HotSpot JVM 中,synchronized 的鎖實現有三種形態:
-
偏向鎖(Biased Locking)
-
設計目的:優化 無競爭的場景。
-
特點:
- 第一次獲得鎖的線程會在對象頭記錄“偏向線程 ID”。
- 之后該線程再次進入同步塊時,直接判斷對象頭,無需真正加鎖。
- 幾乎 零開銷,所以可以看作“鎖=沒鎖”。
-
觸發升級:當 第二個線程嘗試獲取鎖,發現已有偏向線程 ID,就會撤銷偏向,升級為輕量級鎖。
-
-
輕量級鎖(Lightweight Locking)
-
設計目的:優化 低競爭場景。
-
特點:
- 通過 CAS 操作嘗試在對象頭設置鎖記錄。
- 如果成功,線程進入臨界區。
- 如果 CAS 失敗,表示有競爭 → 線程會 自旋 等待持有鎖的線程釋放。
-
優點:避免了線程阻塞/喚醒的高開銷。
-
缺點:自旋消耗 CPU。
-
觸發升級:如果競爭嚴重,自旋失敗,鎖會膨脹為重量級鎖。
-
-
重量級鎖(Heavyweight Locking)
-
設計目的:處理 高競爭場景。
-
特點:
- 線程獲取不到鎖時會進入 阻塞狀態(操作系統層面),等待喚醒。
- 保證安全性,但有 內核態/用戶態切換 的高開銷。
-
適合鎖競爭非常激烈的場景。
-
升級路徑
偏向鎖 → 輕量級鎖 → 重量級鎖
- 鎖只能 升級,不會降級。
- 這是為了避免鎖頻繁轉換帶來的復雜性和額外開銷。
👉 總結:
- 偏向鎖:無競爭優化,幾乎零成本。
- 輕量級鎖:低競爭優化,CAS + 自旋。
- 重量級鎖:高競爭場景,阻塞/喚醒。
- 升級路徑:偏向 → 輕量級 → 重量級(不可逆)。
下一題(問題13)
什么是 適應性自旋(Adaptive Spinning)?它相比固定自旋有什么優勢?
-
自旋的背景
- 輕量級鎖獲取失敗時,線程不會馬上阻塞,而是進入 自旋(忙等)狀態,嘗試再次獲取鎖。
- 固定自旋:早期 JVM 使用固定次數(例如 10 次)循環嘗試獲取鎖。
-
適應性自旋(Adaptive Spinning)
-
自旋次數 不再固定,而是由 JVM 根據運行時情況動態調整。
-
例如:
- 如果某個鎖的持有線程 剛剛釋放過鎖,說明鎖等待時間可能短,可以多自旋幾次。
- 如果某個線程在之前的自旋中 幾乎沒成功過,則減少或放棄自旋,直接阻塞。
-
-
優勢
- 節省開銷:避免無意義的長時間自旋,降低 CPU 消耗。
- 提升性能:在鎖競爭較輕時,減少線程掛起/喚醒的切換開銷。
- 更智能:自旋次數動態適配應用場景,而不是一刀切。
👉 總結:
適應性自旋 = JVM 根據歷史成功經驗和鎖的持有情況,動態調整自旋時間。
優點:比固定自旋更智能,能在輕度競爭時提升性能,在重度競爭時減少 CPU 浪費。
問題14
鎖優化技術的總體目標是什么?為什么說它們的核心思想是“減少獲得鎖的代價”?
-
總體目標
-
鎖優化技術的根本目標是:
- 減少獲取和釋放鎖的性能開銷,提高程序在并發情況下的吞吐量。
-
換句話說:讓
synchronized
這種看似“重量級”的操作,在大多數場景下跑得輕快。
-
-
為什么要減少代價?
- 傳統重量級鎖:競爭失敗就會觸發線程阻塞/喚醒,需要內核態和用戶態切換 → 開銷大。
- 優化手段:通過自旋、偏向鎖、輕量級鎖、鎖消除、鎖粗化等方式,盡量避免線程進入阻塞狀態。
-
核心思想
- 減少上下文切換(用戶態 ? 內核態)。
- 避免不必要的同步(鎖消除)。
- 合并分散鎖操作(鎖粗化)。
- 利用無競爭場景優化(偏向鎖)。
- 低競爭場景用忙等代替阻塞(輕量級鎖 + 自旋)。
👉 總結:
鎖優化的核心思想 = 減少獲得鎖的代價,即讓鎖盡量避免走到“重量級”阻塞/喚醒的路徑,而是通過各種手段(偏向、輕量、自旋、消除、粗化)在用戶態內完成,大幅降低 CPU 和線程調度的開銷。
問題15
請總結 第 13 章《線程安全與鎖優化》 的主要知識點:
- 線程安全的定義與分類
- 互斥同步 / 非阻塞同步 / 無同步方案
- Java 提供的關鍵工具
- JVM 的鎖優化技術(自旋、適應性自旋、鎖消除、鎖粗化、偏向鎖、輕量級鎖、重量級鎖)
按照保證程度,線程安全可以分為:
- 不可變(Immutable):如
String
,天生線程安全; - 絕對線程安全:完全符合嚴格定義,但開銷極大;
- 相對線程安全:常見,保證單次方法調用安全,如
Vector
; - 線程兼容:類本身非線程安全,需要調用方加同步,如
ArrayList
; - 線程對立:不管怎樣都無法在并發場景中正確使用。
實現線程安全的手段主要有三類:
- 互斥同步:最傳統的方法,通過鎖來保證臨界區互斥訪問,典型實現是
synchronized
和ReentrantLock
。 - 非阻塞同步:通過 CAS 等硬件原子指令完成更新,失敗時自旋重試,避免線程阻塞。
- 無同步方案:在沒有共享數據或無需同步時,直接避免鎖,比如局部變量、
ThreadLocal
、不可變對象。
Java 提供了多種工具來實現線程安全,包括:
- 關鍵字:
synchronized
、volatile
、final
(安全發布語義); - 并發包:
Lock
、Atomic
原子類、并發容器(ConcurrentHashMap
)、同步器(Semaphore
、CountDownLatch
); ThreadLocal
等無同步手段。
在 JVM 內部,HotSpot 對鎖做了多種優化以減少性能開銷:
- 自旋鎖:在短時間內忙等而不阻塞,避免上下文切換。
- 適應性自旋:根據歷史和環境動態調整自旋次數。
- 鎖消除:利用逃逸分析去掉無意義的同步。
- 鎖粗化:合并多個連續的加鎖/解鎖操作。
- 偏向鎖:優化無競爭場景,幾乎零成本。
- 輕量級鎖:適合低競爭場景,基于 CAS + 自旋。
- 重量級鎖:高競爭場景下的最終方案,線程阻塞/喚醒。