| 特性 | 說明 |
| 可重入 | 同一線程可多次獲取同一鎖,計數器機制 |
| 互斥 | 同一時刻僅一線程持有鎖 |
| 自動釋放 | 正常退出/異常拋出均自動釋放,無死鎖風險 |
| 內存可見性 | 釋放鎖前刷新到主存,獲取鎖後從主存讀取 |
| 非公平 | 搶鎖機制,不保證等待隊列順序 |
鎖升級優化 (JDK 1.6+)
無鎖 → 偏向鎖 → 輕量級鎖 → 重量級鎖
(單線程) (CAS自旋) (OS互斥量)
- 偏向鎖:首次獲取鎖的線程在對象頭標記 Thread ID,後續進入無需 CAS
- 輕量級鎖:多線程交替訪問,用 CAS 修改對象頭指向棧帧鎖記錄
- 重量級鎖:競爭激烈,膨脹為 ObjectMonitor,涉及 OS 內核態切換
ReentrantLock:顯式鎖標杆
核心 API
Lock lock = new ReentrantLock();
// 基礎用法 (必須 try-finally)
lock.lock();
try {
// 臨界區
} finally {
lock.unlock();
}
// 進階特性
lock.lockInterruptibly(); // 可中斷獲取
lock.tryLock(); // 非阻塞嘗試
lock.tryLock(5, TimeUnit.SECONDS); // 超時嘗試
Condition cond = lock.newCondition(); // 精確等待/通知
cond.await();
cond.signal();
公平 vs 非公平
// 公平鎖:嚴格按請求順序獲取,吞吐率低但無餓死
Lock fairLock = new ReentrantLock(true);
// 非公平鎖:默認,搶佔式,吞吐率高
Lock unfairLock = new ReentrantLock(false);
ReadWriteLock:讀寫分離利器
適用場景
讀多寫少、讀操作耗時長 (如緩存、配置、搜索索引)
核心實現原理
- 單一 `state` 變量:高 16 位 = 讀狀態 (共享鎖計數),低 16 位 = 寫狀態 (獨佔鎖計數 + 重入計數)
- 讀鎖:共享鎖,多線程可同時持有
- 寫鎖:獨佔鎖,排斥所有讀/寫
- 鎖降級:寫鎖 → 讀鎖 → 釋放寫鎖 (支持);鎖升級:不支持
公平性策略
| 模式 | 策略 | 特點 |
| 非公平 | 讀線程可插隊獲取讀鎖 | 高吞吐,寫線程可能餓死 |
| 公平 | 隊首若為寫線程,後續讀線程阻塞等待 | 寫線程無餓死,延遲略高 |
AQS:鎖框架基石
核心組件
AbstractQueuedSynchronizer
├── state (volatile int) // 同步狀態
├── head/tail (Node) // CLH 隊列頭尾
├── acquire/release (模板方法) // 獲取/釋放邏輯
├── tryAcquire/tryRelease (模板) // 子類實現具體語義
└── ConditionObject // 條件隊列
自定義鎖範例:雙工鎖 (允許 2 線程並發)
public class TwoPermitLock extends AbstractQueuedSynchronizer {
@Override
protected int tryAcquireShared(int arg) {
for (;;) {
int current = getState();
int next = current + 1;
if (next > 2) return -1; // 超過許可數
if (compareAndSetState(current, next)) return 1;
}
}
@Override
protected boolean tryReleaseShared(int arg) {
for (;;) {
int current = getState();
int next = current - 1;
if (compareAndSetState(current, next)) return true;
}
}
public void lock() { acquireShared(1); }
public void unlock() { releaseShared(1); }
}
進階鎖:StampedLock (Java 8+)
StampedLock lock = new StampedLock();
// 樂觀讀 (極高性能,無鎖)
long stamp = lock.tryOptimisticRead();
Data data = readData();
if (!lock.validate(stamp)) { // 驗證期間無寫入
// 回退到悲觀讀
stamp = lock.readLock();
try { data = readData(); } finally { lock.unlockRead(stamp); }
}
// 悲觀讀
stamp = lock.readLock();
try { ... } finally { lock.unlockRead(stamp); }
// 寫鎖
stamp = lock.writeLock();
try { ... } finally { lock.unlockWrite(stamp); }
// 讀鎖轉寫鎖 (慎用)
stamp = lock.readLock();
try {
long writeStamp = lock.tryConvertToWriteLock(stamp);
if (writeStamp != 0) {
stamp = writeStamp;
// 寫操作
} else {
lock.unlockRead(stamp);
stamp = lock.writeLock();
// 寫操作
}
} finally { lock.unlock(stamp); }
選型決策指南
| 場景 | 推薦 | 理由 |
| 簡單同步、代碼塊短 | synchronized | JVM 優化、可讀性高、無釋放風險 |
| 需要可中斷、超時、公平性 | ReentrantLock | 功能豐富、顯式控制 |
| 讀多寫少、高併發讀 | ReentrantReadWriteLock | 共享讀鎖、大幅提升讀吞吐 |
| 讀多寫少、極致讀性能 | StampedLock | 樂觀讀無鎖、Java 8+ |
| 信號量控制並發度 | Semaphore | 許可證模型、限流 |
| 線程協作等待 | CountDownLatch/CyclicBarrier | 計數器/柵欄語義 |
避坑指南
- ❌ `synchronized` 鎖住字符串常量 / 基本類型包裝對象 (常量池導致意外共享)
- ❌ `lock.lock()` 後無 `try-finally` 包裹 `unlock()`
- ❌ `ReentrantReadWriteLock` 寫鎖期間嘗試獲取讀鎖 (死鎖)
- ❌ 長時間持有鎖執行 I/O、RPC、睡眠
- ✅ 使用 `tryLock(timeout)` 避免死鎖
- ✅ 縮小鎖粒度:分段鎖、CAS、ThreadLocal、無鎖數據結構
- ✅ 考慮 `CompletableFuture`、Actor 模型替代傳統鎖
總結
理解鎖的本質是「協調共享狀態訪問」。從 `synchronized` 到 `Lock` 再到 `AQS`,是從隱式到顯式、從固定到可擴展的演進。實戰中:**優先用高層抽象 (並發集合、CompletableFuture)、再考慮顯式鎖、最後才是 synchronized**,並在架構層面通過無狀態設計、消息傳遞減少共享狀態。
- 上一篇文章: Java MD5 加密實戰:16位/32位實現、鹽值防碰撞與效能優化
- 下一篇文章: Android/Java MD5 加密工具類:32位/16位、文件校驗與性能優化版
Copyright ©
程式碼之家 All Rights Reserved