
1. 線程安全基礎(chǔ)概念解析在多線程編程中線程安全問題是每個(gè)Java開發(fā)者必須面對的挑戰(zhàn)。當(dāng)多個(gè)線程同時(shí)訪問共享資源時(shí)如果沒有正確的同步機(jī)制就會導(dǎo)致數(shù)據(jù)不一致、程序行為異常等嚴(yán)重后果。理解這些問題的本質(zhì)是構(gòu)建可靠并發(fā)系統(tǒng)的第一步。注意線程安全問題往往在測試階段難以復(fù)現(xiàn)但在生產(chǎn)環(huán)境高并發(fā)場景下會突然爆發(fā)這也是為什么這類問題被稱為八股——因?yàn)樗鼈兙哂械湫托院椭貜?fù)性。2. 八大經(jīng)典線程安全問題剖析2.1 競態(tài)條件(Race Condition)當(dāng)多個(gè)線程對共享數(shù)據(jù)進(jìn)行非原子操作時(shí)最終結(jié)果取決于線程執(zhí)行的時(shí)序。典型場景是計(jì)數(shù)器遞增private int count 0; public void increment() { count; // 非原子操作實(shí)際包含讀取-修改-寫入三步 }這個(gè)簡單的count操作實(shí)際上包含三個(gè)步驟讀取當(dāng)前值、增加1、寫回新值。當(dāng)兩個(gè)線程同時(shí)執(zhí)行時(shí)可能導(dǎo)致增量丟失。解決方案使用synchronized關(guān)鍵字使用AtomicInteger等原子類使用ReentrantLock顯式鎖2.2 內(nèi)存可見性問題由于Java內(nèi)存模型(JMM)的規(guī)定線程對變量的修改可能不會立即對其他線程可見。例如private boolean flag false; // 線程A flag true; // 線程B while(!flag) { // 可能永遠(yuǎn)看不到flag的變化 // do something }解決方案使用volatile關(guān)鍵字使用synchronized塊使用final字段保證初始化可見性2.3 死鎖(Deadlock)四個(gè)必要條件同時(shí)滿足時(shí)會發(fā)生死鎖互斥條件占有并等待非搶占條件循環(huán)等待典型死鎖代碼// 線程A synchronized(resource1) { synchronized(resource2) { // ... } } // 線程B synchronized(resource2) { synchronized(resource1) { // ... } }解決方案按固定順序獲取鎖使用tryLock設(shè)置超時(shí)使用死鎖檢測工具2.4 活鎖(Livelock)線程雖然沒有被阻塞但由于不斷重試相同的操作而無法繼續(xù)執(zhí)行。例如兩個(gè)線程互相禮讓// 線程A while(!acquireLock1()) { releaseLock2(); Thread.yield(); } // 線程B while(!acquireLock2()) { releaseLock1(); Thread.yield(); }解決方案引入隨機(jī)退避機(jī)制重構(gòu)資源獲取邏輯2.5 饑餓(Starvation)某些線程長期得不到執(zhí)行機(jī)會。常見原因線程優(yōu)先級設(shè)置不當(dāng)鎖被某些線程長期占用不公平的鎖策略解決方案使用公平鎖性能會下降合理設(shè)置線程優(yōu)先級避免長時(shí)間持有鎖2.6 上下文切換開銷頻繁的線程切換會導(dǎo)致性能下降。當(dāng)線程數(shù)超過CPU核心數(shù)時(shí)操作系統(tǒng)需要通過上下文切換來調(diào)度線程。優(yōu)化建議使用線程池控制線程數(shù)量減少鎖的持有時(shí)間考慮無鎖編程2.7 偽共享(False Sharing)當(dāng)多個(gè)線程修改位于同一緩存行的不同變量時(shí)會導(dǎo)致不必要的緩存同步。例如class Data { volatile long x; // 與y可能位于同一緩存行 volatile long y; }解決方案使用Contended注解Java 8手動(dòng)填充(padding)調(diào)整數(shù)據(jù)結(jié)構(gòu)布局2.8 線程逃逸對象在未正確構(gòu)造完成時(shí)就被其他線程訪問。例如public class ThisEscape { public ThisEscape(EventSource source) { source.registerListener( new EventListener() { public void onEvent(Event e) { doSomething(e); // 此時(shí)ThisEscape可能未構(gòu)造完成 } }); } }解決方案避免在構(gòu)造函數(shù)中啟動(dòng)線程使用工廠方法使用final字段3. 線程安全實(shí)踐指南3.1 同步工具選擇策略場景推薦方案備注簡單計(jì)數(shù)器AtomicXXX性能最好復(fù)雜狀態(tài)更新synchronized簡單可靠讀多寫少ReentrantReadWriteLock提高并發(fā)性條件等待Condition Lock比wait/notify更靈活延遲初始化volatile double-check避免指令重排序3.2 并發(fā)集合使用技巧Java并發(fā)包提供了多種線程安全的集合類ConcurrentHashMap分段鎖實(shí)現(xiàn)高并發(fā)下性能優(yōu)異CopyOnWriteArrayList適合讀多寫少的場景ConcurrentLinkedQueue無界非阻塞隊(duì)列BlockingQueue接口的各種實(shí)現(xiàn)適合生產(chǎn)者-消費(fèi)者模式注意雖然這些集合是線程安全的但復(fù)合操作如檢查再執(zhí)行仍然需要額外同步。3.3 線程池最佳實(shí)踐根據(jù)任務(wù)類型選擇合適的線程池CPU密集型核心線程數(shù) ≈ CPU核心數(shù)IO密集型核心線程數(shù)可以更大使用ThreadPoolExecutor構(gòu)造函數(shù)而不是Executors工廠方法避免隱藏的問題newFixedThreadPool無界隊(duì)列可能導(dǎo)致OOMnewCachedThreadPool可能創(chuàng)建過多線程合理設(shè)置拒絕策略默認(rèn)的AbortPolicy會拋出異常CallerRunsPolicy讓調(diào)用者線程執(zhí)行任務(wù)自定義策略記錄日志或持久化任務(wù)4. 常見問題排查與調(diào)試4.1 死鎖診斷使用jstack獲取線程轉(zhuǎn)儲jstack pid thread_dump.txt查找deadlock關(guān)鍵詞或分析線程持有的鎖和等待的鎖使用VisualVM或JConsole的線程監(jiān)控功能4.2 性能問題定位使用JFR(Java Flight Recorder)記錄并發(fā)事件分析鎖競爭jstack查看阻塞線程jstat查看線程狀態(tài)分布使用-XX:PrintAssembly查看熱點(diǎn)代碼的匯編指令需要HSDIS4.3 內(nèi)存可見性問題復(fù)現(xiàn)使用-XX:StressLCM -XX:StressGCM選項(xiàng)強(qiáng)制JVM重排序使用jstress工具進(jìn)行并發(fā)壓力測試在x86架構(gòu)上測試后應(yīng)在ARM等多核設(shè)備上再次驗(yàn)證5. 高級并發(fā)模式5.1 無鎖編程CAS(Compare-And-Swap)原理public final int getAndIncrement() { for(;;) { int current get(); int next current 1; if(compareAndSet(current, next)) return current; } }適用場景競爭不激烈時(shí)性能更好避免死鎖風(fēng)險(xiǎn)實(shí)現(xiàn)非阻塞算法5.2 不可變對象通過不可變性保證線程安全public final class ImmutablePoint { private final int x; private final int y; public ImmutablePoint(int x, int y) { this.x x; this.y y; } // 只有g(shù)etter方法沒有setter }優(yōu)點(diǎn)無需同步可以安全共享簡化程序邏輯5.3 線程局部存儲使用ThreadLocal為每個(gè)線程維護(hù)獨(dú)立副本private static final ThreadLocalSimpleDateFormat dateFormat ThreadLocal.withInitial(() - new SimpleDateFormat(yyyy-MM-dd));注意事項(xiàng)使用后需要清理避免內(nèi)存泄漏不適合存儲大量數(shù)據(jù)線程池環(huán)境下要特別小心6. Java內(nèi)存模型深入理解6.1 happens-before規(guī)則程序順序規(guī)則監(jiān)視器鎖規(guī)則volatile變量規(guī)則線程啟動(dòng)規(guī)則線程終止規(guī)則中斷規(guī)則終結(jié)器規(guī)則傳遞性6.2 指令重排序限制JVM會在不影響單線程執(zhí)行結(jié)果的前提下重排序指令但需要遵守happens-before規(guī)則。通過以下方式禁止重排序volatile讀寫synchronized塊final字段VarHandle/Atomic類操作6.3 內(nèi)存屏障類型LoadLoad屏障StoreStore屏障LoadStore屏障StoreLoad屏障開銷最大在x86架構(gòu)上只有StoreLoad屏障是顯式的其他屏障通常由硬件自動(dòng)保證。7. 并發(fā)測試方法論7.1 單元測試使用CountDownLatch協(xié)調(diào)多個(gè)線程CountDownLatch startSignal new CountDownLatch(1); CountDownLatch doneSignal new CountDownLatch(N); for (int i 0; i N; i) new Thread(() - { startSignal.await(); try { // 測試邏輯 } finally { doneSignal.countDown(); } }).start(); startSignal.countDown(); doneSignal.await();使用Thread.yield()增加線程切換概率7.2 壓力測試使用JMH(Java Microbenchmark Harness)進(jìn)行基準(zhǔn)測試使用jstress進(jìn)行并發(fā)壓力測試考慮不同硬件架構(gòu)的測試7.3 確定性測試使用jcstress等工具編寫可重復(fù)的并發(fā)測試JCStressTest Outcome(id 1, 1, expect Expect.ACCEPTABLE_INTERESTING) State public class MyConcurrencyTest { private int x; Actor public void actor1() { x; } Actor public void actor2() { x; } Arbiter public void arbiter(IntResult2 r) { r.r1 x; r.r2 x; } }8. 實(shí)戰(zhàn)經(jīng)驗(yàn)分享在實(shí)際項(xiàng)目中處理線程安全問題時(shí)我總結(jié)了以下幾點(diǎn)經(jīng)驗(yàn)優(yōu)先考慮無共享設(shè)計(jì)通過任務(wù)分解、線程封閉等方式減少共享狀態(tài)保持同步區(qū)域簡單同步塊中的代碼越少越好避免IO操作等耗時(shí)操作文檔記錄線程安全保證明確說明每個(gè)類的線程安全級別不可變(Immutable)無條件線程安全有條件線程安全非線程安全線程對立避免過度同步同步會影響性能并可能引發(fā)死鎖考慮使用更高層次的并發(fā)抽象Future和CompletableFutureForkJoinPool并行流(parallel streams)反應(yīng)式編程(Reactive)監(jiān)控線程狀態(tài)在生產(chǎn)環(huán)境中監(jiān)控死鎖線程阻塞線程創(chuàng)建頻率線程等待時(shí)間理解底層硬件不同CPU架構(gòu)x86 vs ARM的內(nèi)存模型實(shí)現(xiàn)不同保持學(xué)習(xí)關(guān)注Java并發(fā)API的更新如Java 8的StampedLockJava 9的VarHandleJava 21的虛擬線程