首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 綜合教學

Java 內存模型深度解析:鎖的 Happens-Before 語義與 ReentrantLock 實現原理

     編輯:Java 綜合教學

前言

鎖不僅是互斥工具,更是 Java 內存模型(JMM)中實現線程間通信的核心機制。本文從 happens-before 規則、內存語義、ReentrantLock 源碼三個層次,徹底講透鎖的「可見性」保證與底層實現。

一、鎖釋放-獲取建立的 Happens-Before 關係

1.1 代碼示例

class MonitorExample {
    int a = 0;
    public synchronized void writer() {  // 線程 A
        a++;  // 1
    }  // 2 鎖釋放
    public synchronized void reader() {  // 線程 B
        int i = a;  // 3
    }  // 4 鎖獲取
}

1.2 Happens-Before 圖解

Happens-Before 關係圖

  • 黑色箭頭:程序順序規則(同一線程內前後操作)。
  • 橙色箭頭:監視器鎖規則(釋放鎖 hb 獲取鎖)。
  • 藍色箭頭:傳遞性推導出的跨線程可見性保證(2 hb 3)。

結論:線程 A 釋放鎖前對共享變量的所有修改,對線程 B 獲取同一鎖後立即可見。

二、鎖的內存語義

操作內存語義等價 volatile 操作
釋放鎖將本地內存共享變量刷新到主內存volatile 寫
獲取鎖將本地內存置無效,強制從主內存讀取volatile 讀

2.1 三大核心語義

  1. 線程 A 釋放鎖 → 向後續獲取鎖的線程發送「修改消息」。
  2. 線程 B 獲取鎖 → 接收前序線程發送的「修改消息」。
  3. 線程 A 釋放鎖 → 線程 B 獲取鎖 → 實質是線程 A 通過主內存向線程 B 發送消息。

三、ReentrantLock 實現原理深度剖析

3.1 核心結構

  • 依賴 AbstractQueuedSynchronizer (AQS)。
  • AQS 內部維護 volatile int state 表示同步狀態。
  • 分公平鎖與非公平鎖兩種實現。

3.2 公平鎖:讀寫 volatile 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 規則,實現釋放-獲取的內存語義。

3.3 非公平鎖:CAS 同時具備 volatile 讀寫語義

protected final boolean compareAndSetState(int expect, int update) {
    return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
}

關鍵點:JDK 文檔明確聲明 CAS 具有volatile 讀和寫的雙重內存語義。

編譯器層面

  • 禁止 CAS 與其前後任意內存操作重排序。
  • 等價於同時設置 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 手冊):

  1. 確保指令原子性執行。
  2. 禁止該指令與前後指令重排序(全內存屏障)。
  3. 將寫緩衝區刷新到內存,使其他 CPU 可見。

第 2、3 點精確實現了 volatile 讀寫語義。

3.4 總結對比

鎖類型釋放鎖獲取鎖
公平鎖寫 volatile state讀 volatile state
非公平鎖寫 volatile stateCAS 更新 state (volatile 讀+寫)

四、Concurrent 包的實現基石

基於 CAS 的 volatile 雙重語義,Java 線程通信形成四大支柱:

  1. volatile 讀/寫
  2. CAS (compareAndSet)
  3. 鎖釋放/獲取
  4. final 域寫/讀

統一實現模式

// 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)均遵循此模式;高層組件(線程池、並發集合、鎖、屏障)再構建於此基礎之上。

五、總結

  1. 鎖的內存語義本質:釋放鎖 = volatile 寫,獲取鎖 = volatile 讀/CAS。
  2. ReentrantLock 通過 AQS 的 volatile state + CAS 實現語義,公平/非公平僅在獲取策略上不同。
  3. 理解此機制是掌握 java.util.concurrent 核心源碼的鑰匙。
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved