編程實(shí)戰(zhàn):從JMM到線程池調(diào)優(yōu)與故障排查)
簡介本資源是一份面向Java中高級(jí)開發(fā)者與并發(fā)編程學(xué)習(xí)者的系統(tǒng)性PDF講義聚焦JUC核心機(jī)制與多線程實(shí)戰(zhàn)能力提升解決高并發(fā)場景下線程安全、性能瓶頸與鎖優(yōu)化等關(guān)鍵問題。內(nèi)容覆蓋JUC庫整體定位、線程池原理與調(diào)優(yōu)、ConcurrentHashMap等并發(fā)集合的底層邏輯與使用邊界、synchronized與ReentrantLock的對(duì)比演進(jìn)、volatile的可見性/有序性保障、原子類CAS實(shí)現(xiàn)以及鎖粗化、偏向鎖、自適應(yīng)自旋等JVM級(jí)并發(fā)優(yōu)化技術(shù)。資源為1個(gè)838KB的PDF文件結(jié)構(gòu)清晰含典型問題驅(qū)動(dòng)式講解如“Synchronized缺陷與Lock如何彌補(bǔ)”“l(fā)ong/double為何需volatile”“哪些操作真正具備原子性”輔以JMM模型與Happens-Before規(guī)則解析兼顧理論深度與代碼實(shí)踐指導(dǎo)。目前已有209人學(xué)習(xí)下載適合希望夯實(shí)并發(fā)基礎(chǔ)、突破性能調(diào)優(yōu)瓶頸、構(gòu)建高穩(wěn)定多線程應(yīng)用的工程師系統(tǒng)精讀。1. 為什么“Java并發(fā)編程綜合講解”不是講Thread.sleep()和synchronized就完事了很多剛寫完第一個(gè)多線程Demo的開發(fā)者會(huì)誤以為“并發(fā)編程”就是加個(gè)鎖、起幾個(gè)線程、再用個(gè)wait/notify——直到某天線上服務(wù)在QPS剛過800時(shí)突然響應(yīng)延遲飆升到2秒監(jiān)控里線程池隊(duì)列長度持續(xù)突破5000而堆棧日志里全是TIMED_WAITING (parking)或者本地調(diào)試時(shí)明明邏輯正確卻總在第37次運(yùn)行時(shí)復(fù)現(xiàn)一個(gè)ConcurrentModificationException重啟后又消失。這不是玄學(xué)是Java內(nèi)存模型JMM在真實(shí)場景中對(duì)指令重排、可見性、原子性的隱式約束開始反撲。這篇筆記不講概念定義只講我在線上高并發(fā)系統(tǒng)日均請求2.3億、混合IO與計(jì)算密集型任務(wù)、多版本JDK共存環(huán)境下把“Java并發(fā)編程”真正落地為可驗(yàn)證、可壓測、可回滾的工程能力的全過程從volatile到底層匯編屏障怎么映射到CompletableFuture鏈?zhǔn)疆惓鞑楹慰偮┎东@再到ForkJoinPool并行流在容器化環(huán)境里CPU親和性失效的真實(shí)修復(fù)。適合正在寫支付對(duì)賬、實(shí)時(shí)風(fēng)控、批量導(dǎo)出模塊且已踩過至少一次ConcurrentHashMap擴(kuò)容死循環(huán)或ScheduledThreadPoolExecutor任務(wù)堆積的工程師。2. 并發(fā)基石三要素可見性、原子性、有序性如何用代碼證明它們不是理論空談2.1 可見性失效用一個(gè)while(true)循環(huán)親手觸發(fā)JVM的“緩存幻覺”public class VisibilityDemo { private static boolean flag false; public static void main(String[] args) throws InterruptedException { Thread writer new Thread(() - { try { Thread.sleep(1000); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } flag true; System.out.println(Writer: flag set to true); }); Thread reader new Thread(() - { while (!flag) { // 空循環(huán)等待flag變?yōu)閠rue } System.out.println(Reader: flag is now true); }); reader.start(); writer.start(); reader.join(); writer.join(); } }現(xiàn)象程序極大概率卡死在reader線程的while循環(huán)里永不打印Reader: flag is now true原因JVM允許將flag讀取結(jié)果緩存在線程本地如寄存器或CPU L1 cache且無同步機(jī)制強(qiáng)制刷新主內(nèi)存值writer線程修改flag后reader線程看不到該變更驗(yàn)證手段在while循環(huán)內(nèi)添加Thread.yield()或System.out.println()會(huì)大概率讓程序退出——因?yàn)檫@些操作隱式觸發(fā)內(nèi)存屏障迫使線程重新讀取主內(nèi)存根本解法將flag聲明為volatile或用AtomicBoolean或用synchronized塊包裹讀寫2.2 原子性陷阱i不是原子操作但AtomicInteger.incrementAndGet()為什么是public class AtomicityDemo { private static int counter 0; private static AtomicInteger atomicCounter new AtomicInteger(0); public static void main(String[] args) throws InterruptedException { Thread t1 new Thread(() - { for (int i 0; i 10000; i) { counter; atomicCounter.incrementAndGet(); } }); Thread t2 new Thread(() - { for (int i 0; i 10000; i) { counter; atomicCounter.incrementAndGet(); } }); t1.start(); t2.start(); t1.join(); t2.join(); System.out.println(Plain int counter: counter); // 極大概率 20000 System.out.println(AtomicInteger: atomicCounter.get()); // 永遠(yuǎn) 20000 } }關(guān)鍵拆解counter實(shí)際編譯為三條JVM指令iload加載值、iadd加1、istore存回。兩個(gè)線程可能同時(shí)iload到相同舊值如100各自加1后都存回101導(dǎo)致丟失一次更新。AtomicInteger原理incrementAndGet()底層調(diào)用Unsafe.compareAndSwapInt()該方法通過CPU級(jí)別的CASCompare-And-Swap指令實(shí)現(xiàn)僅當(dāng)當(dāng)前值等于預(yù)期值時(shí)才更新否則重試。JVM保證該操作在單條CPU指令層面不可分割。參數(shù)說明compareAndSwapInt(Object obj, long offset, int expected, int x)中offset是value字段在對(duì)象內(nèi)存中的偏移量由Unsafe.objectFieldOffset()獲取expected是期望舊值x是新值。失敗時(shí)返回false需手動(dòng)重試AtomicInteger內(nèi)部已封裝為自旋循環(huán)。2.3 有序性挑戰(zhàn)雙重檢查鎖定DCL為何需要volatile不加會(huì)怎樣public class SingletonDCL { private static volatile SingletonDCL instance; // ← 關(guān)鍵必須volatile private SingletonDCL() {} public static SingletonDCL getInstance() { if (instance null) { synchronized (SingletonDCL.class) { if (instance null) { instance new SingletonDCL(); // ← 這行不是原子操作 } } } return instance; } }問題根源new SingletonDCL()在JVM中分為三步① 分配內(nèi)存② 調(diào)用構(gòu)造函數(shù)初始化對(duì)象③ 將instance引用指向分配的內(nèi)存地址。步驟②和③可能被JVM重排序只要不改變單線程語義。若線程A執(zhí)行到①→③→②此時(shí)instance已非null但對(duì)象未初始化完成線程B進(jìn)入外層if判斷直接返回未初始化的instance后續(xù)調(diào)用其方法時(shí)拋出NullPointerException。volatile作用禁止JVM對(duì)instance的寫操作與構(gòu)造函數(shù)初始化之間的重排序并確保其他線程讀取instance時(shí)能看見其完全初始化后的狀態(tài)即“happens-before”關(guān)系。替代方案使用靜態(tài)內(nèi)部類Holder模式或枚舉更簡潔且天然線程安全。3. 線程池實(shí)戰(zhàn)從Executors工廠方法到ThreadPoolExecutor源碼級(jí)參數(shù)調(diào)優(yōu)3.1 Executors.newFixedThreadPool(10)的三個(gè)致命缺陷// ? 危險(xiǎn)寫法隱藏風(fēng)險(xiǎn)生產(chǎn)環(huán)境禁用 ExecutorService executor Executors.newFixedThreadPool(10);缺陷1無界隊(duì)列LinkedBlockingQueuenewFixedThreadPool底層使用LinkedBlockingQueue容量Integer.MAX_VALUE當(dāng)任務(wù)提交速度持續(xù)高于處理速度時(shí)隊(duì)列無限膨脹最終OOM。缺陷2拒絕策略粗暴默認(rèn)AbortPolicy直接拋RejectedExecutionException業(yè)務(wù)無感知錯(cuò)誤日志淹沒在海量請求中。缺陷3線程名不可控線程名為pool-1-thread-1無法關(guān)聯(lián)業(yè)務(wù)模塊排查線程阻塞時(shí)定位困難。3.2 手動(dòng)構(gòu)建ThreadPoolExecutor6個(gè)核心參數(shù)的取舍邏輯public class CustomThreadPool { public static ExecutorService buildCustomPool() { return new ThreadPoolExecutor( 4, // corePoolSize常駐線程數(shù)設(shè)為CPU核數(shù)*2IO密集型或CPU核數(shù)CPU密集型 16, // maxPoolSize最大線程數(shù)防止突發(fā)流量打垮系統(tǒng) 60L, TimeUnit.SECONDS, // keepAliveTime空閑線程存活時(shí)間避免頻繁創(chuàng)建銷毀開銷 new ArrayBlockingQueue(100), // workQueue有界隊(duì)列容量預(yù)估峰值QPS * 平均處理耗時(shí)秒此處100為示例 new ThreadFactoryBuilder() // 自定義線程工廠設(shè)置有意義的線程名 .setNameFormat(payment-batch-%d) .setDaemon(false) .build(), new RejectedExecutionHandler() { // 自定義拒絕策略記錄指標(biāo)降級(jí) Override public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) { Metrics.counter(thread_pool_rejected, pool, payment-batch).increment(); // 降級(jí)邏輯寫入本地磁盤隊(duì)列異步重試 LocalRetryQueue.offer(r); } } ); } }參數(shù)決策依據(jù)corePoolSize某支付對(duì)賬系統(tǒng)實(shí)測CPU密集型任務(wù)如RSA驗(yàn)簽設(shè)為Runtime.getRuntime().availableProcessors()效果最佳IO密集型如HTTP調(diào)用設(shè)為2 * availableProcessors()因線程常阻塞在Socket讀寫。workQueue選ArrayBlockingQueue而非SynchronousQueue前者提供緩沖能力后者要求每個(gè)任務(wù)必須立即有空閑線程承接否則直接觸發(fā)拒絕策略。RejectedExecutionHandler線上必須實(shí)現(xiàn)Metrics打點(diǎn)業(yè)務(wù)降級(jí)不能僅打印日志。3.3 線程池監(jiān)控如何發(fā)現(xiàn)“線程泄漏”和“任務(wù)堆積”public class ThreadPoolMonitor { private final ThreadPoolExecutor executor; public ThreadPoolMonitor(ThreadPoolExecutor executor) { this.executor executor; } public void reportStatus() { int activeCount executor.getActiveCount(); long completedTaskCount executor.getCompletedTaskCount(); long taskCount executor.getTaskCount(); int queueSize executor.getQueue().size(); // 關(guān)鍵指標(biāo)任務(wù)堆積率 隊(duì)列中任務(wù)數(shù) / 已完成任務(wù)數(shù) 隊(duì)列中任務(wù)數(shù) double backlogRatio (double) queueSize / Math.max(1, completedTaskCount queueSize); // 報(bào)警閾值根據(jù)業(yè)務(wù)SLA調(diào)整 if (backlogRatio 0.3) { Alert.send(ThreadPoolBacklogHigh, String.format(pool%s, active%d, queue%d, ratio%.2f, payment-batch, activeCount, queueSize, backlogRatio)); } // 記錄到Prometheus ThreadPoolMetrics.activeThreads.labels(payment-batch).set(activeCount); ThreadPoolMetrics.queueSize.labels(payment-batch).set(queueSize); } }血淚經(jīng)驗(yàn)?zāi)炒未蟠偾皦簻ygetActiveCount()穩(wěn)定在16maxPoolSize但getQueue().size()每分鐘增長200getCompletedTaskCount()幾乎停滯——這是典型的下游依賴如數(shù)據(jù)庫連接池耗盡導(dǎo)致線程全部阻塞在getConnection()上無法釋放。此時(shí)activeCount高但completedTaskCount低是線程池“假性健康”的典型信號(hào)。4. 并發(fā)工具類深度解析從CountDownLatch到Phaser何時(shí)該換4.1 CountDownLatch vs CyclicBarrier一個(gè)不能重用一個(gè)能重用但真實(shí)瓶頸在哪兒// 場景啟動(dòng)服務(wù)時(shí)需等待3個(gè)模塊DB、Cache、MQ全部就緒后才開放HTTP端口 public class ServiceStartup { private final CountDownLatch latch new CountDownLatch(3); public void startModules() { new Thread(this::initDB).start(); new Thread(this::initCache).start(); new Thread(this::initMQ).start(); } private void initDB() { // ... DB連接初始化 latch.countDown(); // 模塊就緒計(jì)數(shù)減1 } private void awaitAllReady() throws InterruptedException { latch.await(); // 主線程阻塞直到計(jì)數(shù)歸零 openHttpPort(); } }CountDownLatch局限性一旦計(jì)數(shù)歸零await()立即返回countDown()再調(diào)用無效無法重復(fù)用于“每分鐘檢查一次健康狀態(tài)”的場景。CyclicBarrier適用場景某風(fēng)控系統(tǒng)需每分鐘聚合10個(gè)節(jié)點(diǎn)的實(shí)時(shí)特征所有節(jié)點(diǎn)必須同時(shí)提交數(shù)據(jù)才能進(jìn)入下一步計(jì)算。CyclicBarrier支持reset()重置且可設(shè)置Runnable barrierAction在每次屏障觸發(fā)時(shí)執(zhí)行匯總邏輯。4.2 Phaser動(dòng)態(tài)注冊/注銷線程解決“成員不確定”的協(xié)同難題public class DynamicPhaser { private final Phaser phaser new Phaser(1); // 初始階段數(shù)1主線程 public void registerWorker(String workerId) { int phase phaser.register(); // 動(dòng)態(tài)注冊新參與者返回當(dāng)前階段號(hào) System.out.println(workerId registered at phase phase); } public void runWorker(String workerId) { // 工作線程執(zhí)行任務(wù) doWork(workerId); phaser.arriveAndAwaitAdvance(); // 到達(dá)屏障等待所有已注冊者到達(dá) } public void deregisterWorker(String workerId) { phaser.arriveAndDeregister(); // 主動(dòng)注銷不再參與后續(xù)階段 System.out.println(workerId deregistered); } private void doWork(String workerId) { // 模擬工作 try { Thread.sleep(100); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } } }Phaser優(yōu)勢支持動(dòng)態(tài)增減參與者register()/arriveAndDeregister()適合長連接網(wǎng)關(guān)中連接數(shù)動(dòng)態(tài)變化的場景arriveAndAwaitAdvance()比CountDownLatch.await()更輕量無鎖競爭可查詢當(dāng)前階段號(hào)getPhase()用于實(shí)現(xiàn)分階段超時(shí)控制。注意Phaser在JDK 7引入部分老系統(tǒng)如JDK 6需降級(jí)為CyclicBarrier固定數(shù)組管理。4.3 CompletableFuture鏈?zhǔn)疆惒骄幊痰摹昂蠡谒帯睓C(jī)制public class AsyncChaining { public CompletableFutureString fetchUserAndOrder(Long userId) { return CompletableFuture.supplyAsync(() - userDao.findById(userId)) .thenCompose(user - { if (user null) { return CompletableFuture.failedFuture(new UserNotFoundException(User not found)); } return CompletableFuture.supplyAsync(() - orderService.findLatestOrder(user.getId())); }) .handle((order, throwable) - { if (throwable ! null) { log.error(Failed to fetch order for user {}, userId, throwable); return default_order; // 提供兜底值 } return order ! null ? order.toString() : no_order; }); } }關(guān)鍵設(shè)計(jì)thenCompose()用于扁平化嵌套CompletableFuture避免CompletableFutureCompletableFutureThandle()是真正的“后悔藥”無論前面成功或失敗都會(huì)執(zhí)行且可返回新值或拋出新異常exceptionally()僅在異常時(shí)執(zhí)行但無法訪問正常結(jié)果適用場景更窄。避坑supplyAsync()默認(rèn)使用ForkJoinPool.commonPool()若任務(wù)含阻塞IO如DB查詢需顯式傳入自定義線程池否則會(huì)拖垮整個(gè)公共池。5. 并發(fā)常見問題排查5個(gè)真實(shí)翻車現(xiàn)場與根因定位法5.1 現(xiàn)象應(yīng)用啟動(dòng)后CPU持續(xù)100%jstack顯示大量線程處于BLOCKED狀態(tài)現(xiàn)象jstack pid輸出中數(shù)十個(gè)線程狀態(tài)為java.lang.Thread.State: BLOCKED (on object monitor)且都在等待同一把鎖例如- waiting to lock 0x000000071a2b3c40原因某段高頻調(diào)用的代碼使用synchronized修飾實(shí)例方法且該實(shí)例為單例如Spring Bean導(dǎo)致所有線程串行執(zhí)行。定位jstack中找到鎖ID如0x000000071a2b3c40搜索locked 0x000000071a2b3c40找到持有該鎖的線程通常只有1個(gè)查看該線程的堆棧定位到具體方法和行號(hào)解決將synchronized粒度縮小到臨界區(qū)代碼塊或改用ReentrantLock配合tryLock(timeout)避免無限等待。5.2 現(xiàn)象定時(shí)任務(wù)偶發(fā)跳過執(zhí)行日志顯示ScheduledThreadPoolExecutor的scheduleAtFixedRate間隔變長現(xiàn)象配置scheduleAtFixedRate(task, 0, 5, SECONDS)但某次任務(wù)執(zhí)行耗時(shí)6秒下一次執(zhí)行時(shí)間不是第10秒而是第11秒且后續(xù)周期持續(xù)漂移。原因scheduleAtFixedRate的“固定速率”指從上一次任務(wù)開始時(shí)間算起而非結(jié)束時(shí)間。若任務(wù)執(zhí)行時(shí)間 間隔后續(xù)任務(wù)會(huì)立即排隊(duì)但ScheduledThreadPoolExecutor的線程數(shù)不足時(shí)排隊(duì)任務(wù)無法及時(shí)執(zhí)行。驗(yàn)證檢查getQueue().size()是否持續(xù)增長getActiveCount()是否長期等于getPoolSize()。解決增加線程池大小改用scheduleWithFixedDelay從上一次結(jié)束時(shí)間算起任務(wù)內(nèi)加超時(shí)控制Future.get(5, SECONDS)超時(shí)則取消并記錄告警。5.3 現(xiàn)象ConcurrentHashMap在JDK 7下發(fā)生死循環(huán)CPU飆高現(xiàn)象JDK 7環(huán)境ConcurrentHashMap.get()方法內(nèi)for循環(huán)永不退出jstack顯示線程在Segment.get()中自旋。原因JDK 7ConcurrentHashMap擴(kuò)容時(shí)通過頭插法遷移鏈表多線程并發(fā)擴(kuò)容可能導(dǎo)致鏈表成環(huán)A→B→A。JDK 8已改為紅黑樹尾插法徹底解決。規(guī)避升級(jí)JDK 8若無法升級(jí)避免在擴(kuò)容高峰期如緩存預(yù)熱進(jìn)行高頻寫操作。5.4 現(xiàn)象ThreadLocal內(nèi)存泄漏Full GC后ThreadLocalMap$Entry對(duì)象仍不回收現(xiàn)象使用ThreadLocal存儲(chǔ)大對(duì)象如Listbyte[]線程池復(fù)用線程后ThreadLocal變量未remove()導(dǎo)致Entry的value強(qiáng)引用大對(duì)象keyThreadLocal雖為弱引用但value無法釋放。驗(yàn)證jmap -histo pid | grep ThreadLocalMap$Entry觀察數(shù)量是否隨請求量線性增長。解決必須在finally塊中調(diào)用threadLocal.remove()使用InheritableThreadLocal需額外注意父子線程傳遞后的清理Spring的RequestContextHolder已內(nèi)置remove()但自定義ThreadLocal必須手動(dòng)管理。5.5 現(xiàn)象ReentrantLock.lock()后未在finally中unlock()導(dǎo)致線程永久阻塞現(xiàn)象jstack中多個(gè)線程BLOCKED在ReentrantLock.lock()且持有鎖的線程已TERMINATED或WAITING在其他地方。原因lock()后發(fā)生異常unlock()未執(zhí)行鎖未釋放。根治lock.lock(); try { // 臨界區(qū)代碼 } finally { lock.unlock(); // 必須必須必須 }進(jìn)階防護(hù)使用tryLock(long time, TimeUnit unit)設(shè)置超時(shí)避免無限等待。6. 生產(chǎn)級(jí)并發(fā)驗(yàn)證用JMH壓測Arthas診斷火焰圖定位性能拐點(diǎn)6.1 用JMH驗(yàn)證鎖優(yōu)化效果從synchronized到StampedLock的吞吐量對(duì)比Fork(3) Warmup(iterations 5, time 1, timeUnit TimeUnit.SECONDS) Measurement(iterations 10, time 1, timeUnit TimeUnit.SECONDS) State(Scope.Benchmark) OutputTimeUnit(TimeUnit.MICROSECONDS) public class LockBenchmark { private final Object syncLock new Object(); private final ReentrantLock reentrantLock new ReentrantLock(); private final StampedLock stampedLock new StampedLock(); Benchmark public void syncMethod(Blackhole blackhole) { synchronized (syncLock) { blackhole.consume(Math.random()); } } Benchmark public void reentrantLockMethod(Blackhole blackhole) { reentrantLock.lock(); try { blackhole.consume(Math.random()); } finally { reentrantLock.unlock(); } } Benchmark public void stampedLockReadMethod(Blackhole blackhole) { long stamp stampedLock.tryOptimisticRead(); if (stampedLock.validate(stamp)) { blackhole.consume(Math.random()); // 樂觀讀成功 } else { stamp stampedLock.readLock(); try { blackhole.consume(Math.random()); } finally { stampedLock.unlockRead(stamp); } } } }關(guān)鍵參數(shù)說明Fork(3)每個(gè)測試fork 3個(gè)JVM進(jìn)程避免JIT編譯污染W(wǎng)armup預(yù)熱5輪讓JIT充分優(yōu)化Measurement正式測量10輪每輪1秒結(jié)果解讀某次實(shí)測StampedLock樂觀讀場景吞吐量是synchronized的3.2倍但寫操作writeLock()性能略低于ReentrantLock。結(jié)論讀多寫少場景首選StampedLock寫頻繁則用ReentrantLock。6.2 Arthas診斷實(shí)時(shí)查看線程池狀態(tài)與慢任務(wù)堆棧# 連接目標(biāo)JVM $ as.sh --select demo-app # 查看線程池實(shí)時(shí)狀態(tài)替換為你的線程池Bean名 $ threadpool --poolName paymentBatchExecutor # 輸出示例 # poolName : paymentBatchExecutor # poolSize : 16 # activeCount : 16 # queuedTaskCount : 234 # completedTaskCount : 124567 # largestPoolSize : 16 # taskCount : 124801 # 定位執(zhí)行超時(shí)的任務(wù)假設(shè)任務(wù)類名含PaymentTask $ thread -n 5 .*PaymentTask.* # 輸出最近5個(gè)匹配線程的堆??焖俣ㄎ蛔枞c(diǎn)如數(shù)據(jù)庫連接、遠(yuǎn)程調(diào)用實(shí)戰(zhàn)技巧threadpool命令需目標(biāo)應(yīng)用已暴露ThreadPoolTaskExecutor的ManagedResource注解或通過--poolClass指定類路徑thread -n 5后跟正則精準(zhǔn)過濾業(yè)務(wù)線程避免被GC線程干擾結(jié)合watch命令監(jiān)控特定方法入?yún)?返回值watch com.example.PaymentService processPayment {params,returnObj} -x 3。6.3 火焰圖定位識(shí)別CPU熱點(diǎn)中的并發(fā)瓶頸# 1. 采集30秒CPU火焰圖需perf支持 $ sudo perf record -F 99 -p pid -g -- sleep 30 $ sudo perf script out.perf # 2. 生成火焰圖需FlameGraph工具 $ ./stackcollapse-perf.pl out.perf out.folded $ ./flamegraph.pl out.folded flame.svg # 3. 分析關(guān)鍵特征 # - 若java.util.concurrent.locks.AbstractQueuedSynchronizer占據(jù)高寬鎖競爭嚴(yán)重 # - 若java.lang.Thread.sleep或java.net.SocketInputStream.socketRead0集中IO阻塞 # - 若java.util.HashMap.get或java.util.ArrayList.get高頻出現(xiàn)集合類非并發(fā)安全。我的習(xí)慣每次上線新并發(fā)模塊必做三件事JMH壓測基線、Arthas部署后首查線程池、大促前全鏈路火焰圖采樣對(duì)ConcurrentHashMap永遠(yuǎn)用computeIfAbsent替代get put避免兩次哈希查找CompletableFuture鏈中絕不混用join()和get()統(tǒng)一用join()不拋檢異?;騡et()需try-catch避免意外中斷。希望幫到你。本文還有配套的精品資源點(diǎn)擊獲取