高併發核心概念釐清
1. 同步 vs 非同步 (方法調用維度)
- 同步調用:呼叫端**阻塞等待**被調用方法返回結果
- 非同步調用:呼叫端**立即返回**,被調用工作在背景執行緒續行 (Future/Callback/EventLoop)
2. 併發 vs 並行 (執行維度)
| 概念 | 定義 | 單 CPU | 多 CPU |
| 併發 | 同一時間段內處理多個任務 (時間片輪轉) | ✓ 可能 | ✓ 可能 |
| 並行 | 同一時刻物理上同時執行多個任務 | ✗ 不可能 | ✓ 可能 |
3. 臨界區
共享資源/數據的訪問代碼區域。**互斥性**:同一時刻僅允許一個執行緒進入。其他執行緒需等待 (阻塞/自旋)。
4. 阻塞 vs 非阻塞
- 阻塞:執行緒因資源不可用被 OS 掛起 (Context Switch,~80k CPU cycles)
- 非阻塞:執行緒持續嘗試 (CAS/樂觀鎖),不進入 WAITING 狀態
5. 死鎖、飢餓、活鎖
| 現象 | 成因 | 特徵 | 難度 |
| 死鎖 | 循環等待資源 (A 鎖 1 等 2,B 鎖 2 等 1) | 靜態卡死、CPU 0%、易發現 | 中 |
| 活鎖 | 雙方禮讓釋放資源,反覆重試 (如走路避人) | 動態忙碌但無進展、CPU 高、難發現 | 高 |
| 飢餓 | 低優先級執行緒長期搶不到資源 | 特定執行緒無法推進 | 中 |
併發進階:無鎖、無阻塞、無等待層級
1. 阻塞同步 (傳統 synchronized/Lock)
悲觀策略:假設衝突必發生,加鎖排他。缺點:上下文切換開銷大、優先級反轉、死鎖風險。
2. 無阻塞 - 阻塞自由
- 任意執行緒在有限步驟內完成操作
- 衝突時回滾重試 (如資料庫樂觀鎖)
- 可能**活鎖** (持續衝突重試無進展)
3. 無鎖
- 滿足無阻塞
- **保證至少一個執行緒在有限步驟內成功** (系統整體推進)
- 典型:
CAS (Compare-And-Swap) 迴圈
// 經典無鎖計數器
while (!atomicVar.compareAndSet(localVal, localVal + 1)) {
localVal = atomicVar.get(); // 重新讀取最新值重試
}
4. 無等待 (最高層級)
- 滿足無鎖
- **所有執行緒**都在有限步驟內完成 (無飢餓)
- 實現極難,典型:複製資料修改 + 原子替換 (寫者複製、修改副本、CAS 替換原始引用)
兩大擴容法則:效能上限量化
Amdahl 定律:固定問題規模的加速比上限
公式:Speedup = 1 / [(1 - P) + P/N]
P:可並行化比例 (0~1)
N:處理器核心數
1-P:串行部分 (不可並行)
啟示:串行部分決定加速上限。P=0.9, N=100 → Speedup ≈ 9.17。**優化串行瓶頸比單純加 CPU 更有效**。
Gustafson 定律:固定時間擴大問題規模
公式:Speedup = N - F(N - 1) (F = 串行比例)
- 假設問題規模隨 CPU 增長 (如更大矩陣、更多用戶)
- 串行部分佔比隨規模增長而下降
- **結論**:只要並行部分足夠大,加速比近似與 CPU 核心數**線性增長**
實務架構指導原則
- 優先無鎖/無等待資料結構:
ConcurrentHashMap、LongAdder、StampedLock
- 鎖粒度最小化:縮小臨界區、分段鎖、讀寫分離
- 不可變物件:天然執行緒安全、無鎖共享
- 非阻塞演算法:CAS 迴圈、Copy-on-Write、Ring Buffer
- 避免過早優化:先正確、再測量、再優化熱點
- 理解硬體特性:緩存行填充、偽共享、記憶體屏障、NUMA