線程安全是並發編程的核心課題,《Java Concurrency in Practice》專書闡述其奧義。本文聚焦 JVM 層面,系統剖析線程安全分級、實現機制與鎖優化策略。
《Java Concurrency in Practice》定義:多線程訪問對象無需外部同步即正確運行,則該對象線程安全。Java 中可分五級:
final 修飾,構造後狀態凍結(如 String、Integer)。天生線程安全,無同步開銷。Vector 復合操作仍需同步)。Vector、HashTable、Collections.synchronizedXxx)。synchronized/Lock 等手段實現安全(如 ArrayList、HashMap)。最常見。Thread.suspend/resume)。核心思想:共享數據同一時刻僅允許一個線程訪問。互斥是手段,同步是目的。
monitorenter/monitorexit 字節碼,自動配對,無遺漏風險。JDK 1.6 引入偏向鎖、輕量級鎖、鎖消除、鎖粗化、自適應自旋等優化,性能大幅提升。lock()/unlock() 配對,必須在 finally 中 unlock,否則極易死鎖。JDK 1.6 後 synchronized 性能追平甚至超越,ReentrantLock 更多作為進階功能補充。CountDownLatch、CyclicBarrier、Semaphore 等同步工具類。基於硬件指令 Compare-And-Swap (CAS),樂觀鎖策略:
java.util.concurrent.atomic 包(AtomicInteger、AtomicReference 等)// AtomicInteger.getAndIncrement() 源碼精髓
public final int getAndIncrement() {
for (;;) { // 自旋循環
int current = get();
int next = current + 1;
if (compareAndSet(current, next)) // CAS 嘗試更新
return current;
}
}
優勢:無線程阻塞/喚醒開銷,吞吐量高(低競爭時)。劣勢:高競爭下自旋消耗 CPU、ABA 問題(可用 AtomicStampedReference 解決)。
從根源消除共享:
避免線程阻塞/喚醒開銷,假設鎖很快釋放,讓線程忙等待(自旋)。自適應自旋:JVM 根據歷史自旋成功率動態調整自旋次數,成功則增加,失敗則減少甚至放棄自旋。
JIT 編譯器通過逃逸分析,發現鎖對象僅在當前線程使用(無逃逸),則直接消除鎖操作。典型場景:StringBuffer、Vector 等線程安全類的局部使用。
原則上鎖粒度越細越好,但同一鎖反復加釋放(特別是循環中)開銷巨大。JVM 將多個連續同步塊合併為一個大同步塊,減少鎖操作次數。
無競爭時,使用 CAS 將鎖標記記錄在對象頭和棧幀中,避免重量級互斥量開銷。競爭升級為重量級鎖。
無競爭且持續由同一線程獲取時,消除同步開銷。對象頭記錄線程 ID,後續進出同步塊僅需檢查 ID。競爭時撤銷偏向,升級為輕量級/重量級鎖。
java.util.concurrent 高級工具(ConcurrentHashMap、ThreadPoolExecutor、CompletableFuture 等)ThreadLocal 解決線程隔離