🔒 ReentrantLock 实现原理:银行VIP金库的智能锁系统

113 阅读5分钟

将用一家"线程银行"的故事,带你彻底理解ReentrantLock的源码实现。这家银行有一个VIP金库,使用了一套叫 AQS(Abstract Queued Synchronizer)  的智能锁系统,这正是ReentrantLock的核心!

🏦 故事背景:线程银行的VIP金库

想象一家银行:

  • VIP金库 = 共享资源
  • 客户 = 线程(Thread)
  • 银行经理 = ReentrantLock
  • 排队系统 = AQS(抽象队列同步器)

金库使用一套智能锁系统,包含两种模式:

  • 非公平模式(默认):新客户可以插队
  • 公平模式:严格先来后到

🔧 核心组件:银行智能锁系统

📜 银行设施规划图(源码结构)

java

public class ReentrantLock implements Lock {
    private final Sync sync; // 锁系统的核心大脑
    
    // 同步器基类(银行基础系统)
    abstract static class Sync extends AbstractQueuedSynchronizer {
        // 实现锁的核心方法
        abstract void lock();
        final boolean nonfairTryAcquire(int acquires) { /*...*/ }
        protected final boolean tryRelease(int releases) { /*...*/ }
    }
    
    // 非公平模式系统
    static final class NonfairSync extends Sync {
        final void lock() { /*...*/ }
        protected final boolean tryAcquire(int acquires) { /*...*/ }
    }
    
    // 公平模式系统
    static final class FairSync extends Sync {
        final void lock() { /*...*/ }
        protected final boolean tryAcquire(int acquires) { /*...*/ }
    }
    
    public ReentrantLock(boolean fair) {
        sync = fair ? new FairSync() : new NonfairSync();
    }
}

🧩 组件功能解析

组件银行比喻技术作用
ReentrantLock银行经理对外提供锁服务
Sync基础锁系统AQS的定制实现
NonfairSync允许插队的业务系统非公平锁实现
FairSync严格排队的业务系统公平锁实现
AQS排队管理系统管理线程队列和状态
state金库使用计数牌0=空闲,>0=被占用次数
CLH队列客户等候区存储等待线程的双向链表

🔐 上锁流程:客户进入金库

场景1:非公平模式(默认)

java

// NonfairSync.lock()
final void lock() {
    // 第一步:尝试直接插队获取金库
    if (compareAndSetState(0, 1)) 
        setExclusiveOwnerThread(Thread.currentThread());
    else
        acquire(1); // 加入排队系统
}

// AQS.acquire()
public final void acquire(int arg) {
    if (!tryAcquire(arg) && 
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
}

🎬 故事发展:

  1. 新客户A到达金库
  2. 尝试插队:直接查看金库是否空闲(state=0)
  3. 成功:立即进入,state设为1
  4. 失败:去排队区取号等待

场景2:公平模式

java

// FairSync.lock()
final void lock() {
    acquire(1); // 直接排队,不尝试插队
}

// FairSync.tryAcquire()
protected final boolean tryAcquire(int acquires) {
    final Thread current = Thread.currentThread();
    int c = getState();
    if (c == 0) {
        // 关键区别:检查是否有前辈在排队
        if (!hasQueuedPredecessors() && 
            compareAndSetState(0, acquires)) {
            setExclusiveOwnerThread(current);
            return true;
        }
    }
    // 重入锁逻辑(同非公平)
    else if (current == getExclusiveOwnerThread()) {
        // ...
    }
    return false;
}

🎬 故事发展:

  1. 新客户B到达金库
  2. 直接取号排队,不尝试插队
  3. 只有当自己是队首且金库空闲时才能进入

🔍 深入AQS排队系统源码

🏗️ AQS核心结构

java

public abstract class AbstractQueuedSynchronizer {
    // 队首节点(虚节点)
    private transient volatile Node head;
    // 队尾节点
    private transient volatile Node tail;
    // 同步状态:0=空闲,>0=占用次数
    private volatile int state;
    
    // 排队节点
    static final class Node {
        volatile Node prev;      // 前驱节点
        volatile Node next;      // 后继节点
        volatile Thread thread;  // 排队的线程
        int waitStatus;          // 等待状态
    }
}

📥 加入排队队列:addWaiter()

java

private Node addWaiter(Node mode) {
    Node node = new Node(mode);
    for (;;) { // 自旋直到成功
        Node oldTail = tail;
        if (oldTail != null) {
            // 设置新节点的前驱
            node.setPrevRelaxed(oldTail);
            // CAS更新队尾
            if (compareAndSetTail(oldTail, node)) {
                oldTail.next = node;
                return node;
            }
        } else {
            initializeSyncQueue(); // 初始化队列
        }
    }
}

😴 队列中等待:acquireQueued()

java

final boolean acquireQueued(final Node node, int arg) {
    boolean interrupted = false;
    try {
        for (;;) { // 自旋
            final Node p = node.predecessor();
            // 如果前驱是头节点(自己是第二个)
            if (p == head && tryAcquire(arg)) {
                setHead(node); // 获取成功,自己成为头节点
                p.next = null; // 帮助GC
                return interrupted;
            }
            // 检查是否需要阻塞
            if (shouldParkAfterFailedAcquire(p, node))
                interrupted |= parkAndCheckInterrupt();
        }
    } catch (Throwable t) {
        cancelAcquire(node);
        throw t;
    }
}

🛌 线程阻塞:parkAndCheckInterrupt()

java

private final boolean parkAndCheckInterrupt() {
    LockSupport.park(this); // 阻塞当前线程
    return Thread.interrupted(); // 返回中断状态
}

🔓 解锁流程:客户离开金库

java

public void unlock() {
    sync.release(1); // 释放一个占用计数
}

// AQS.release()
public final boolean release(int arg) {
    if (tryRelease(arg)) { // 尝试释放
        Node h = head;
        if (h != null && h.waitStatus != 0)
            unparkSuccessor(h); // 唤醒后继节点
        return true;
    }
    return false;
}

// Sync.tryRelease()
protected final boolean tryRelease(int releases) {
    int c = getState() - releases;
    // 检查是否是当前线程持有锁
    if (Thread.currentThread() != getExclusiveOwnerThread())
        throw new IllegalMonitorStateException();
    
    boolean free = false;
    if (c == 0) { // 完全释放
        free = true;
        setExclusiveOwnerThread(null);
    }
    setState(c); // 更新状态
    return free;
}

⚡ 唤醒后续节点:unparkSuccessor()

java

private void unparkSuccessor(Node node) {
    int ws = node.waitStatus;
    if (ws < 0) 
        compareAndSetWaitStatus(node, ws, 0); // 清除状态
    
    Node s = node.next;
    if (s == null || s.waitStatus > 0) {
        s = null;
        // 从后向前找有效节点(处理取消的节点)
        for (Node t = tail; t != null && t != node; t = t.prev)
            if (t.waitStatus <= 0) s = t;
    }
    if (s != null)
        LockSupport.unpark(s.thread); // 唤醒线程
}

🔄 重入锁实现原理

当同一个客户多次进入金库:

java

// NonfairSync.nonfairTryAcquire()
final boolean nonfairTryAcquire(int acquires) {
    final Thread current = Thread.currentThread();
    int c = getState();
    if (c == 0) {
        // 尝试获取锁...
    }
    // 重入关键:当前线程已持有锁
    else if (current == getExclusiveOwnerThread()) {
        int nextc = c + acquires; // 增加计数
        if (nextc < 0) throw new Error("Maximum lock count exceeded");
        setState(nextc);
        return true;
    }
    return false;
}
  • state记录重入次数:每lock()一次+1,unlock()一次-1
  • 完全释放条件:state归0时才释放锁

⚖️ 公平 vs 非公平实现差异

公平锁的额外检查

java

public final boolean hasQueuedPredecessors() {
    Node h, s;
    if ((h = head) != null) {
        if ((s = h.next) == null || s.thread != Thread.currentThread())
            return true; // 存在有效后继节点
    }
    return false;
}
场景非公平锁公平锁
新线程获取锁可直接插队尝试获取必须排队
吞吐量更高(减少线程切换)略低
饥饿问题可能发生不会发生
实现复杂度简单需额外检查队列
适用场景大多数高并发场景严格要求公平性的场景

🧠 AQS 队列状态变化图

deepseek_mermaid_20250626_cc4f57.png


💡 技术总结

ReentrantLock 实现三大支柱:

  1. AQS(AbstractQueuedSynchronizer)

    • CLH变体队列管理等待线程
    • state变量表示锁状态
    • 提供acquire/release模板方法
  2. CAS(Compare and Swap)

    • 实现无锁状态更新
    • 保证state和队列操作的原子性
    • 关键方法:compareAndSetState()
  3. LockSupport

    • 线程阻塞(park())和唤醒(unpark())的底层机制
    • Object.wait()/notify()更灵活

设计精髓:

  • 模板方法模式:AQS提供骨架,子类实现具体策略
  • 可重入设计:通过state计数支持重复加锁
  • 双模式支持:公平/非公平策略可配置
  • 高性能队列:CLH变体减少锁竞争

🌟 一句话记住
ReentrantLock = AQS队列管理 + CAS原子操作 + LockSupport线程控制
就像银行的智能VIP金库系统,既高效又公平地管理着线程访问!

掌握了这些原理,你就理解了Java并发包中最核心的锁实现机制!