鎖不僅是互斥工具,更是 Java 內存模型(JMM)中實現線程間通信的核心機制。本文從 happens-before 規則、內存語義、ReentrantLock 源碼三個層次,徹底講透鎖的「可見性」保證與底層實現。
class MonitorExample {
int a = 0;
public synchronized void writer() { // 線程 A
a++; // 1
} // 2 鎖釋放
public synchronized void reader() { // 線程 B
int i = a; // 3
} // 4 鎖獲取
}

結論:線程 A 釋放鎖前對共享變量的所有修改,對線程 B 獲取同一鎖後立即可見。
| 操作 | 內存語義 | 等價 volatile 操作 |
|---|---|---|
| 釋放鎖 | 將本地內存共享變量刷新到主內存 | volatile 寫 |
| 獲取鎖 | 將本地內存置無效,強制從主內存讀取 | volatile 讀 |
AbstractQueuedSynchronizer (AQS)。volatile int state 表示同步狀態。// 加鎖 - 讀 volatile state
protected final boolean tryAcquire(int acquires) {
int c = getState(); // 1. 讀 volatile state
if (c == 0) { ... }
...
}
// 釋放鎖 - 寫 volatile state
protected final boolean tryRelease(int releases) {
int c = getState() - releases;
...
setState(c); // 2. 寫 volatile state
return free;
}
機制:釋放鎖時最後寫 volatile state;獲取鎖時首先讀 volatile state。根據 volatile hb 規則,實現釋放-獲取的內存語義。
protected final boolean compareAndSetState(int expect, int update) {
return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
}
關鍵點:JDK 文檔明確聲明 CAS 具有volatile 讀和寫的雙重內存語義。
LoadLoad + LoadStore + StoreStore + StoreLoad 屏障。x86 下 unsafe.cpp 最終生成 lock cmpxchg 指令:
#define LOCK_IF_MP(mp) __asm cmp mp, 0 \
__asm je L0 \
__asm _emit 0xF0 \ // lock 前綴
__asm L0:
inline jint Atomic::cmpxchg(...) { ... LOCK_IF_MP(mp) cmpxchg ... }
lock 前綴效果(Intel 手冊):
第 2、3 點精確實現了 volatile 讀寫語義。
| 鎖類型 | 釋放鎖 | 獲取鎖 |
|---|---|---|
| 公平鎖 | 寫 volatile state | 讀 volatile state |
| 非公平鎖 | 寫 volatile state | CAS 更新 state (volatile 讀+寫) |
基於 CAS 的 volatile 雙重語義,Java 線程通信形成四大支柱:
// 1. 聲明 volatile 狀態
private volatile int state;
// 2. 修改狀態時使用 CAS
public void modify() {
while (!compareAndSetState(old, new)) { /* retry */ }
}
// 3. 讀取狀態直接讀 volatile
public int get() { return state; }
AQS、非阻塞數據結構、原子變量類(java.util.concurrent.atomic)均遵循此模式;高層組件(線程池、並發集合、鎖、屏障)再構建於此基礎之上。
volatile state + CAS 實現語義,公平/非公平僅在獲取策略上不同。java.util.concurrent 核心源碼的鑰匙。