多線程編程充滿陷阱,本文針對同步粒度選擇、線程本地存儲、易失性語義、非原子陷阱、原子字段更新器五大關鍵點,結合字節碼與 JMM 原理深度解析。
// 同步方法:僅 5 字節碼指令
public synchronized int get() { return i; }
// 字節碼:aload_0, getfield, ireturn (ACC_SYNCHRONIZED 標誌由 JVM 隱式處理)
// 同步塊:16 字節碼指令
public int get() { synchronized(this) { return i; } }
// 字節碼:monitorenter, monitorexit, 異常處理額外指令
結論:同步方法字節碼精簡,JVM 內置鎖釋放與異常處理;同步塊靈活控制鎖範圍但開銷大。**優先同步方法,需縮小臨界區時用同步塊。**
核心值:為每個線程提供獨立變量副本,避免參數層層傳遞。
// 鏈路追蹤場景
static final ThreadLocal<TraceContext> TRACE_CTX = ThreadLocal.withInitial(TraceContext::new);
// 線程啟動時初始化
TRACE_CTX.set(new TraceContext(traceId, spanId));
// 全鏈路任意處獲取
TraceContext ctx = TRACE_CTX.get();
// 線程結束必須清理,防內存洩漏
finally { TRACE_CTX.remove(); }
避坑指南:線程池場景必須手動 remove();InheritableThreadLocal 傳遞給子線程但會複製父線程值,注意可變對象共享風險。
JMM 語義:
致命誤區:
volatile int count = 0;
// 非原子!實際為:read -> modify -> write 兩次同步間隙有競態
count++;
// 正確做法:
AtomicInteger count = new AtomicInteger();
count.incrementAndGet(); // 或 synchronized
| 類別 | 適用場景 | 注意事項 |
|---|---|---|
| AtomicInteger/Long/Reference | 獨立計數器、引用更新 | 所有操作(含 get/set)均同步,粒度較粗 |
| AtomicIntegerFieldUpdater | 現有類的 volatile 字段原子更新 | 基於反射,字段必須 volatile、非 static、可見性正確 |
// 書籍閱讀狀態原子更新
class Reader {
private volatile Book currentBook;
static final AtomicReferenceFieldUpdater<Reader, Book> UPDATER =
AtomicReferenceFieldUpdater.newUpdater(Reader.class, Book.class, "currentBook");
void changeBook(Book newBook) {
UPDATER.compareAndSet(this, this.currentBook, newBook); // CAS 無鎖
}
}
java.util.concurrent 高級組件(ConcurrentHashMap、BlockingQueue、CompletableFuture)final 字段、無 Setter、防御性拷貝jstack 死鎖排查、JMH 基準測試、ThreadSanitizer 數據競爭檢測