ReentrantLock源码解析:深入剖析可重入锁的实现

54 阅读8分钟

ReentrantLock源码解析:深入剖析可重入锁的实现

本文将深入解析Java中ReentrantLock的源码,重点分析其核心逻辑、锁的初次上锁、后续上锁以及释放过程。我们将从全局视角出发,逐步拆解代码脉络,并模拟面试场景,通过层层提问检验对ReentrantLock的理解。文章结构清晰,先提供鸟瞰图,再细化分析,最后通过模拟面试加深理解。


一、ReentrantLock全局鸟瞰

ReentrantLock是Java并发包(java.util.concurrent.locks)中的一个可重入互斥锁,功能上与synchronized类似,但提供了更高的灵活性和扩展性。它基于**AbstractQueuedSynchronizer(AQS)**实现,支持公平锁和非公平锁两种模式。

1.1 核心组件与结构

ReentrantLock的实现依赖以下几个关键部分:

  • Sync类ReentrantLock的内部抽象类,继承自AQS,负责核心锁逻辑。

    • NonfairSync:非公平锁实现,允许线程插队。
    • FairSync:公平锁实现,保证先到先得。
  • AQS状态(state) :使用int类型的state表示锁的状态:

    • state == 0:锁未被占用。
    • state > 0:锁被占用,数值表示重入次数。
  • 独占线程(exclusiveOwnerThread) :记录当前持有锁的线程。

  • 核心方法

    • lock():获取锁,可能阻塞。
    • tryLock():尝试获取锁,非阻塞。
    • unlock():释放锁。
    • newCondition():创建条件变量,支持线程等待与唤醒。

1.2 工作原理概览

ReentrantLock通过AQS的state和队列机制管理锁的获取与释放:

  1. 初次上锁:线程尝试将state从0设置为1,并记录自己为锁拥有者。
  2. 重入上锁:同一线程再次获取锁,state加1。
  3. 释放锁:每次释放将state减1,当state为0时,锁完全释放。
  4. 竞争处理:当锁被占用,线程进入AQS的等待队列,公平锁和非公平锁在竞争策略上有所不同。

1.3 公平锁与非公平锁

  • 非公平锁:新线程可能插队,直接尝试获取锁,可能导致后到的线程优先获取。
  • 公平锁:严格按照FIFO顺序,检查队列中是否有等待线程,防止插队。

二、ReentrantLock源码拆解

以下按照逻辑脉络拆解ReentrantLock的核心源码,重点分析锁的实现细节。

2.1 类结构与构造函数

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

    public ReentrantLock() {
        sync = new NonfairSync();
    }

    public ReentrantLock(boolean fair) {
        sync = fair ? new FairSync() : new NonfairSync();
    }
}
  • SyncReentrantLock的核心逻辑委托给Sync,通过构造函数选择公平锁或非公平锁。
  • 默认非公平锁:无参构造函数创建NonfairSync实例。
  • 公平锁选项:通过fair参数选择FairSync

2.2 Sync类与AQS

SyncReentrantLock的内部抽象类,继承自AQS,提供锁的实现基础。

abstract static class Sync extends AbstractQueuedSynchronizer {
    abstract boolean initialTryLock();

    final void lock() {
        if (!initialTryLock())
            acquire(1);
    }

    protected final boolean tryRelease(int releases) {
        int c = getState() - releases;
        if (getExclusiveOwnerThread() != Thread.currentThread())
            throw new IllegalMonitorStateException();
        boolean free = (c == 0);
        if (free)
            setExclusiveOwnerThread(null);
        setState(c);
        return free;
    }
}
  • initialTryLock() :由子类(FairSyncNonfairSync)实现,定义锁的初始尝试逻辑。
  • lock() :尝试获取锁,若失败则调用AQS的acquire方法进入队列。
  • tryRelease(int releases) :释放锁,减少state,当state为0时清除锁拥有者。

2.3 NonfairSync(非公平锁)

static final class NonfairSync extends Sync {
    final boolean initialTryLock() {
        Thread current = Thread.currentThread();
        if (compareAndSetState(0, 1)) {
            setExclusiveOwnerThread(current);
            return true;
        } else if (getExclusiveOwnerThread() == current) {
            int c = getState() + 1;
            if (c < 0) // overflow
                throw new Error("Maximum lock count exceeded");
            setState(c);
            return true;
        }
        return false;
    }

    protected final boolean tryAcquire(int acquires) {
        if (getState() == 0 && compareAndSetState(0, acquires)) {
            setExclusiveOwnerThread(Thread.currentThread());
            return true;
        }
        return false;
    }
}
  • initialTryLock

    • state == 0,通过CAS尝试将state设为1,成功则记录当前线程为锁拥有者。
    • 若锁已由当前线程持有,state加1,支持重入。
  • tryAcquire:仅在state == 0时尝试获取锁,非公平锁不检查队列。

2.4 FairSync(公平锁)

static final class FairSync extends Sync {
    final boolean initialTryLock() {
        Thread current = Thread.currentThread();
        int c = getState();
        if (c == 0) {
            if (!hasQueuedThreads() && compareAndSetState(0, 1)) {
                setExclusiveOwnerThread(current);
                return true;
            }
        } else if (getExclusiveOwnerThread() == current) {
            if (++c < 0) // overflow
                throw new Error("Maximum lock count exceeded");
            setState(c);
            return true;
        }
        return false;
    }

    protected final boolean tryAcquire(int acquires) {
        if (getState() == 0 && !hasQueuedPredecessors() &&
            compareAndSetState(0, acquires)) {
            setExclusiveOwnerThread(Thread.currentThread());
            return true;
        }
        return false;
    }
}
  • initialTryLock

    • 检查hasQueuedThreads(),确保没有其他线程在队列中等待。
    • state == 0且队列为空,通过CAS获取锁。
    • 支持重入,逻辑与非公平锁相同。
  • tryAcquire:检查hasQueuedPredecessors(),保证公平性。

2.5 核心方法

  • lock()

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

    调用Synclock方法,先尝试initialTryLock,失败则进入AQS队列。

  • unlock()

    public void unlock() {
        sync.release(1);
    }
    

    调用AQS的release方法,触发tryRelease释放锁。

  • tryLock()

    public boolean tryLock() {
        return sync.tryLock();
    }
    

    非公平尝试获取锁,不进入队列。


三、锁的生命周期分析

以下详细分析锁的初次上锁、后续上锁和释放过程,以非公平锁为例(公平锁类似,仅在竞争策略上不同)。

3.1 初次上锁

场景:锁未被任何线程持有(state == 0exclusiveOwnerThread == null)。

  1. 调用lock() :线程调用reentrantLock.lock(),触发NonfairSync.initialTryLock()

  2. 检查状态

    • state == 0,通过compareAndSetState(0, 1)尝试将state设为1。
    • CAS成功,设置exclusiveOwnerThread为当前线程,返回true,锁获取成功。
  3. 竞争失败

    • 若CAS失败(其他线程抢先获取锁),调用acquire(1),线程进入AQS等待队列。
    • AQS通过tryAcquire再次尝试获取锁,若仍失败,线程阻塞。

关键点

  • 使用CAS确保原子性。
  • 非公平锁允许新线程插队,可能导致队列中的线程继续等待。

3.2 后续上锁(重入)

场景:当前线程已持有锁(state > 0exclusiveOwnerThread == 当前线程)。

  1. 调用lock() :再次调用lock(),进入initialTryLock()

  2. 检查拥有者

    • getExclusiveOwnerThread() == current,确认是当前线程。
    • state加1,记录重入次数。
  3. 溢出检查

    • state溢出(超过Integer.MAX_VALUE),抛出Error
  4. 返回:直接返回true,无需进入队列。

关键点

  • 重入只需更新state,效率高。
  • state记录重入次数,支持嵌套锁。

3.3 释放锁

场景:线程调用unlock()释放锁。

  1. 调用unlock() :触发sync.release(1),调用tryRelease(1)

  2. 检查拥有者

    • exclusiveOwnerThread != 当前线程,抛出IllegalMonitorStateException
  3. 更新状态

    • state减1。
    • state == 0,设置exclusiveOwnerThread = null,锁完全释放。
  4. 唤醒线程

    • 若锁完全释放,AQS唤醒队列中的下一个线程尝试获取锁。

关键点

  • 仅当state == 0时锁才完全释放。
  • 释放后,队列中的线程(或新线程)竞争锁。

四、模拟面试:层层拷打

以下模拟面试官对ReentrantLock的提问,从基础到深入,检验理解深度。

4.1 基础问题

Q1ReentrantLocksynchronized的区别是什么?

  • AReentrantLock是显式锁,提供更多功能,如:

    • 支持公平锁和非公平锁。
    • 可中断锁(lockInterruptibly)。
    • 条件变量(Condition),支持多条件等待。
    • 尝试锁(tryLock),非阻塞获取。
  • synchronized是隐式锁,简单但功能有限,依赖JVM内置监视器。

Q2ReentrantLock的公平锁和非公平锁有什么不同?

  • A

    • 非公平锁:新线程可插队,优先尝试CAS获取锁,可能导致后到先得,吞吐量较高。
    • 公平锁:检查队列,优先让最早等待的线程获取锁,保证FIFO,防止饥饿,但吞吐量较低。

4.2 深入问题

Q3ReentrantLock如何实现可重入性?

  • A

    • 通过state记录锁的重入次数。
    • initialTryLock中,若exclusiveOwnerThread是当前线程,直接state++
    • 释放时,state--,仅当state == 0时清除exclusiveOwnerThread

Q4:非公平锁的“插队”机制具体如何实现?

  • A

    • NonfairSync.initialTryLock中,新线程直接尝试compareAndSetState(0, 1),不检查队列。
    • 若CAS成功,新线程抢占锁,即使队列中有等待线程。
    • 公平锁则通过hasQueuedThreads()hasQueuedPredecessors()检查队列,防止插队。

4.3 刁钻问题

Q5:如果线程A持有锁,线程B和C在队列等待,A释放锁后会发生什么?

  • A

    • 非公平锁:A释放锁后,B(队列头部)尝试获取锁,但新线程D也可能通过CAS插队抢锁。
    • 公平锁:A释放锁后,B(队列头部)优先获取锁,新线程D必须排队。
    • AQS通过unparkSuccessor唤醒队列头部线程,具体结果取决于锁类型和竞争。

Q6ReentrantLockstate溢出时会怎样?

  • A

    • stateint类型,最大值为Integer.MAX_VALUE(2^31-1)。
    • 重入时,若state + 1 < 0,表示溢出,抛出Error("Maximum lock count exceeded")
    • 实际场景中,需避免过度重入。

4.4 场景问题

Q7:假设你在调试代码,发现IllegalMonitorStateException,可能是什么原因?

  • A

    • 调用unlock()时,当前线程不是锁的持有者(exclusiveOwnerThread != 当前线程)。
    • 调用Conditionawaitsignal时,未持有锁。
    • 解决方法:确保lock()unlock()成对调用,检查线程逻辑。

Q8:如何使用ReentrantLock实现一个线程安全的计数器?

  • A
class Counter {
    private final ReentrantLock lock = new ReentrantLock();
    private int count = 0;

    public void increment() {
        lock.lock();
        try {
            count++;
        } finally {
            lock.unlock();
        }
    }

    public int getCount() {
        lock.lock();
        try {
            return count;
        } finally {
            lock.unlock();
        }
    }
}
  • 使用try-finally确保锁释放。
  • 每个操作都加锁,保证线程安全。

五、总结

ReentrantLock是Java并发编程中的重要工具,基于AQS实现了高效、可重入的锁机制。通过分析源码,我们了解了:

  • 全局结构SyncNonfairSyncFairSync的分工。
  • 核心逻辑state管理锁状态,CAS确保原子性。
  • 生命周期:初次上锁依赖CAS,后续上锁更新state,释放时逐步减少state
  • 公平性:非公平锁高吞吐,公平锁防饥饿。
    通过模拟面试,我们进一步巩固了对ReentrantLock的理解,涵盖了原理、实现和实际应用。