JDK 8 針對高併發場景引入三大重量級增強:**LongAdder 解決原子計數器熱點**、**CompletableFuture 重塑異步編程**、**StampedLock 突破讀寫鎖寫飢餓**。本文深度剖析原理、實戰場景與性能基準。
AtomicLong 單一 value 字段,高併發下大量線程 CAS 失敗重試 → **指數級退化**。
base(低併發時直接 CAS 更新)Cell[] 數組,每個 Cell 獨立 valuesum() = base + Σ Cell[i].value// 核心邏輯簡化
public void add(long x) {
Cell[] cs; long b;
if ((cs = cells) != null || !casBase(b = base, b + x)) { // 嘗試 base CAS
if (cs == null) { // 首次擴容
cells = new Cell[2];
cells[getProbe() & 1] = new Cell(x);
} else {
// 線程探針哈希定位 Cell,CAS 更新
// 失敗則雙倍擴容數組,重新哈希
}
}
}
| 場景 | AtomicLong | LongAdder | 建議 |
|---|---|---|---|
| 低併發(<10 線程) | 極快(無 Cell 開銷) | 略慢(Cell 維護) | AtomicLong |
| 中高併發(>10 線程) | 崩潰(CAS 失敗風暴) | 線性擴展(分段並行) | LongAdder |
| 純計數/統計 | 適用 | **首選** | LongAdder |
| 需 CAS 語義(如 compareAndSet) | 支持 | **不支持** | AtomicLong |
LongAdder 是**「空間換時間 + 自適應擴容」**,本質上將單點熱點分散為多點並行,犧牲 get() 求和開銷換取 add() 極致吞吐。
get() 阻塞等待,**無法主動觸發完成**,也無法鏈式組合CompletionStage(40+ 方法),支持 **主動完成**、**流式鏈式調用**、**多任務組合**、**異常處理**| 類別 | 代表方法 | 語義 |
|---|---|---|
| 創建/完成 | supplyAsync(), complete(), completeExceptionally() | 異步啟動、主動設置結果/異常 |
| 單任務轉換 | thenApply(), thenAccept(), thenRun() | 有返回值轉換、消費結果、無返回值執行 |
| 組合多任務 | thenCombine(), thenCompose(), allOf(), anyOf() | 兩任務合併、串行依賴、全等待、任一等待 |
| 異常處理 | exceptionally(), handle(), whenComplete() | 異常恢復、統一處理、完成後回調 |
// 1. 基礎異步 + 主動完成
CompletableFuture<Integer> future = new CompletableFuture<>();
new Thread(() -> {
try { Thread.sleep(1000); } catch (InterruptedException e) {}
future.complete(60); // 主動觸發完成!
}).start();
System.out.println(future.get() * future.get()); // 3600
// 2. 流式鏈式調用
CompletableFuture.supplyAsync(() -> calc(50)) // 異步計算
.thenApply(i -> Integer.toString(i)) // 轉字符串
.thenApply(str -> "\"" + str + "\"") // 加引號
.thenAccept(System.out::println) // 消費打印
.join(); // 等待完成
// 3. 串行依賴組合
CompletableFuture.supplyAsync(() -> calc(50))
.thenCompose(i -> CompletableFuture.supplyAsync(() -> calc(i))) // 依賴上一結果
.thenApply(str -> "\"" + str + "\"")
.thenAccept(System.out::println)
.join();
// 4. 並行聚合
CompletableFuture.allOf(
CompletableFuture.supplyAsync(() -> task1()),
CompletableFuture.supplyAsync(() -> task2())
).thenAccept(v -> System.out.println("全部完成")).join();
讀鎖共享導致「讀多寫少」場景下,寫線程長期無法獲取獨佔鎖 → **寫飢餓**。
writeLock()(排他)、readLock()(悲觀共享)、tryOptimisticRead()(樂觀無鎖)long stamp,解鎖需傳入 stampstamp = tryOptimisticRead() 獲取當前版本戳validate(stamp) 驗證版本未變 → **讀成功**readLock() 悲觀讀取class Point {
private double x, y;
final StampedLock sl = new StampedLock();
// 寫操作:排他鎖
void move(double deltaX, double deltaY) {
long stamp = sl.writeLock();
try { x += deltaX; y += deltaY; }
finally { sl.unlockWrite(stamp); }
}
// 讀操作:樂觀讀 → 降級悲觀训
double distanceFromOrigin() {
long stamp = sl.tryOptimisticRead(); // 樂觀讀
double curX = x, curY = y;
if (!sl.validate(stamp)) { // 版本變了
stamp = sl.readLock(); // 降級悲觀讀
try { curX = x; curY = y; }
finally { sl.unlockRead(stamp); }
}
return Math.sqrt(curX * curX + curY * curY);
}
}
locked 標記,線程自旋檢查前驅節點 while (pred.locked) {}LockSupport.park() 掛起| 鎖類型 | 讀吞吐 | 寫延遲 | 適用場景 | 注意事項 |
|---|---|---|---|---|
| synchronized | 低 | 低 | 簡單臨界區 | 無條件等待 |
| ReentrantReadWriteLock | 高 | **高(飢餓)** | 讀多寫少、寫不頻繁 | 寫飢餓風險 |
| StampedLock | **極高(樂觀讀)** | 低 | **讀多寫少、寫延遲敏感** | **不可重入**;樂觀讀需處理降級 |
JDK 8 並發新特性解決了三大經典痛點:
實戰中:**統計計數首選 LongAdder**、**業務編排首選 CompletableFuture**、**高頻讀低頻寫首選 StampedLock**。掌握這三把利劍,JDK 8+ 高併發開發將得心應手。