ReentrantLock 源码深入分析(基于 JDK 8)

一、什么是 ReentrantLock?

ReentrantLock 是 Java 中一种可重入锁(Reentrant Lock),功能上类似于 synchronized,但提供了更高的控制力,例如:

  • 可中断锁获取
  • 尝试获取锁
  • 公平锁和非公平锁
  • 手动加锁/释放锁
  • 支持条件变量(Condition)

总体设计

public class ReentrantLock implements Lock, java.io.Serializable {
    private final Sync sync;

    // 公平锁构造器
    public ReentrantLock(boolean fair) {
        sync = fair ? new FairSync() : new NonfairSync();
    }

    // 默认非公平锁
    public ReentrantLock() {
        sync = new NonfairSync();
    }

    public void lock() {
        sync.lock();
    }

    public void unlock() {
        sync.release(1); // 调用 AQS 的 release
    }
}

核心:ReentrantLock 实际上是一个 对 AQS 的封装,真正的锁实现逻辑在内部类 Sync 中。

二、AQS 简介(AbstractQueuedSynchronizer)

核心特性:

  • int state:表示同步状态(锁是否持有、重入次数等)
  • CLH 队列:用于管理获取失败的线程(FIFO)
  • 模板方法模式:子类只需实现 tryAcquire / tryRelease 即可
protected final boolean compareAndSetState(int expect, int update);
protected final void setState(int newState);
protected final int getState();

三、非公平锁实现

static final class NonfairSync extends Sync {
    final void lock() {
        // 尝试直接 CAS 抢锁
        if (compareAndSetState(0, 1))
            setExclusiveOwnerThread(Thread.currentThread());
        else
            acquire(1); // AQS 模板方法
    }

    protected final boolean tryAcquire(int acquires) {
        final Thread current = Thread.currentThread();
        int c = getState();

        if (c == 0) {
            // 尝试获取锁,不判断队列是否有前驱线程(非公平)
            if (compareAndSetState(0, acquires)) {
                setExclusiveOwnerThread(current);
                return true;
            }
        } else if (current == getExclusiveOwnerThread()) {
            // 重入
            int nextc = c + acquires;
            setState(nextc);
            return true;
        }
        return false;
    }
}

解读:

  • 非公平锁的策略是:不管队列里有没有线程,先尝试一次 CAS 抢锁再说
  • 成功就直接拿到锁,可能插队,吞吐量高,但会造成“线程饥饿”。

四、公平锁实现

static final class FairSync extends Sync {
    final void lock() {
        acquire(1);
    }

    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()) {
            // 重入
            int nextc = c + acquires;
            setState(nextc);
            return true;
        }
        return false;
    }
}

解读:

  • hasQueuedPredecessors():检查队列中是否有前驱线程,如果有就必须排队。
  • 公平锁避免插队,但带来性能开销。

五、释放锁的逻辑

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); // 支持可重入锁,state为0才真正释放
    return free;
}

解读:

  • 重入锁通过 state 计数:每次 lock() 增加,unlock() 减少
  • state == 0 时才释放锁
  • 锁释放后唤醒队列中的下一个线程(由 AQS 控制)

六、队列等待与唤醒流程(AQS 精髓)

AQS 使用一个 CLH(双向链表)队列 来管理竞争失败的线程:

head <-> Node1 <-> Node2 <-> tail

获取锁失败时:

  • 当前线程被封装成 Node 加入队列尾部
  • 线程进入 park 状态(阻塞等待)

锁释放时:

  • AQS 将 head 的下一个节点唤醒(unpark)
  • 唤醒的线程重新尝试获取锁

七、lock() 的完整流程(非公平锁)

Thread A:进入 lock()
  -> try CAS(state == 0),成功则 setOwner
  -> 否则调用 acquire(1)
      -> tryAcquire() 失败
      -> 入队 + park(阻塞)
Thread B:unlock()
  -> release(1)
     -> setState(0),setOwner(null)
     -> unpark(queue.next)

八、可视化调用图(简化版)

ReentrantLock.lock()
 └──> Sync.lock()
      ├──> tryAcquire()
      └──> acquire(1) → AQS模板 → 阻塞/入队

ReentrantLock.unlock()
 └──> Sync.release(1)
      └──> tryRelease()
      └──> AQS唤醒后继节点

九、总结:ReentrantLock 的底层机制精华

机制 实现方式 说明
重入 state 计数 + 线程标记 支持一个线程重复获取锁
线程排队 CLH 双向队列 线程获取失败则入队
加锁 lock()acquire(1)tryAcquire() 公平或非公平策略
释放 unlock()release(1)tryRelease() 唤醒下一个等待线程
阻塞/唤醒 LockSupport.park()/unpark() 线程挂起与唤醒机制

十、附加建议 & 常见误区

  • 不要忘记 unlock()!:不释放锁会导致其他线程永久阻塞
  • 不可跨线程 unlock():否则抛出 IllegalMonitorStateException
  • 避免死锁:注意多个锁嵌套的顺序,尽量使用 tryLock(timeout) 方式替代 lock()
posted @ 2025-07-26 10:20  零1零1  阅读(55)  评论(0)    收藏  举报