多執行緒不等於高效能。本文從 Amdahl 定律切入,量化分析同步代碼比例對擴展性的決定性影響,隨後詳細講解六大降低鎖競爭技巧:縮小同步塊、分離鎖、分段鎖、原子操作、避開熱點欄位、拒絕物件池,均附完整基準測試程式碼與實測數據,是併發效能調優的實戰手冊。
若同步代碼佔比為 B,CPU 核心數為 n,最大加速比 = 1 / (B + (1-B)/n)。
當 n → ∞,加速比趨近 1/B。
結論:同步比例 50% 時極限加速 2 倍;要達 100 倍加速,同步比例必須 < 1%。
| 技巧 | 核心做法 | 實測提升 |
|---|---|---|
| 縮小同步塊 | 只鎖住共享資料操作,UUID/數值計算移出 | 420 ms → 370 ms (-11%) |
| 分離鎖 | 不同計數器用不同鎖物件 | 56 ms → 38 ms (-32%) |
| 讀寫鎖 | ReentrantReadWriteLock 讀讀並行、寫寫互斥 | 讀多寫少場景顯著提升 |
| 分段鎖 | 參考 ConcurrentHashMap 16 段鎖 | 適用大型共享 Map/集合 |
| 原子操作 | AtomicLong CAS 取代 synchronized | 39 ms → 16 ms (-58%) |
| 避開熱點欄位 | 不維護 size 計數器,查詢時即時加總 | 消除每次寫入的同步開銷 |
| 拒絕物件池 | 新建物件比同步池更快,減輕 GC 壓力用逃逸分析 | 避免池同步成為新瓶頸 |
// 分離鎖版
class CounterSeparateLock {
private final Object customerLock = new Object();
private final Object shippingLock = new Object();
private long customerCount, shippingCount;
void incrementCustomer() { synchronized(customerLock) { customerCount++; } }
// ...
}
// 原子操作版(最快)
class CounterAtomic {
private final AtomicLong customerCount = new AtomicLong();
private final AtomicLong shippingCount = new AtomicLong();
void incrementCustomer() { customerCount.incrementAndGet(); }
// ...
}
ThreadLocalRandom、Thread.yield() 模擬競爭再測試。new ReentrantReadWriteLock(true))可保證 FIFO 順序,避免飢餓。掌握這些模式,併發效能調優不再靠猜,有數據、有原理、有程式碼可落地。