接口 Lock

ReentrantLock 是接口 Lock (java.util.concurrent.locks) 的一个实现类。Lock 接口定义了以下方法:


void lock();
获取锁。如果锁不可获取,则当前线程可能被调度(被),并在获取锁成功之前处于阻塞状态。

boolean tryLock();
仅在锁可以获取的时候获取锁。如果可获取,则使调用的线程获取锁,并立即返回 true。如果锁不可获取,则立刻返回 false。

boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
在指定时间内尝试获取锁。如果线程在开始尝试获取锁之前就被打断,则抛出 InterruptedException 异常。如果在指定时间内获取到锁,返回 true,否则返回 false。

void unlock();
释放锁。

Condition newCondition();
返回一个绑定到此锁实例的 Condition 实例。
在调用 Condition.await() 进入等待之前,调用的线程必须拥有锁的控制权。调用 Condition.await() 后,锁的控制权将会被释放,由其他线程竞争。 同理,调用 Condition.signal() 也需要拥有锁的控制权,但调用后不会自动释放锁,只有手动调用 unlock() 后其他线程才有机会竞争到锁。


从接口定义,我们可以大概想象出 ReentrantLock 的使用图景。
除了 ReentrantLock,Lock 接口还有 ReadLock、WriteLock 等其他实现类,它们的实现细节虽有不同,但是都可以在遵循 Lock 定义的基础上以相同的方式使用。

AbstractQueuedSynchronizer

AbstractQueuedSynchronizer 简称 AQS,是 JUC 的一个核心抽象类,用于支持各种需要串行化(多个线程排队依次占用某个资源)的场景。
AQS 由两部分组成:

  1. 一个双向链表,头节点和尾节点被 volatile 修饰,通过 CAS 操作向尾部添加节点。
  2. 一个状态值,类型为 int,被 volatile 修饰。

此外,AQS 从父类 AbstractOwnableSynchronizer 继承了 exclusiveOwnerThread 属性,储存当前拥有 AQS 的排他所有权的线程实例。

这是一个 AQS 的调用关系图,可以观察到,AQS 的核心方法就是图中间的 acquire 方法,它负责调用子类实现的 tryAcquire 等方法尝试获取资源,如果失败,则将线程包装成节点入队并挂起:

AQS调用结构.png

这是核心 acquire 方法的执行逻辑:

acquire逻辑终极版.png

acquire 方法的执行逻辑可以简单地总结为,每次创建节点后、入队后、自旋次数消耗后、挂起节点前,都自旋到循环开头尝试获取资源。
release 方法则很简单:

release方法逻辑.png
子类会在 tryRelease 方法中做各种检查、设置状态,而 AQS 只负责根据返回结果决定是否唤醒下一个节点。

ReentrantLock 内部的 Sync

ReentrantLock 的内部类 Sync 继承了 AQS,复写 isHeldExclusively()、tryRelease() 两个方法。isHeldExclusively 直接比对 exclusiveOwnerThread 和当前线程;tryRelease 则是统一的逻辑:检查当前线程是否与 exclusiveOwnerThread 相同,如果不相同则抛出 IllegalMonitorStateException 异常。如果相同,则让状态递减,直到状态为 0,这是为了实现可重入锁的重复解锁。

Sync还封装了 tryLock()、lock()、lockInterruptibly()、tryLockNanos(long nanos) 方法,这些是统一的上锁逻辑。留下了两个抽象方法:initialTryLock() 和 tryAcquire()。

而后,ReentrantLock 的另外两个内部类 FairSync 和 NonfairSync 继承了 Sync,分别实现了 initialTryLock() 和 tryAcquire()。

我们先看看 ReentrantLock.lock() 的执行流程:
ReentrantLock.lock() -> sync.lock() -> sync.initialTryLock() --失败--> sync.super.acquire(1) -> sync.tryAcquire(1)

然而我们知道,公平锁和非公平锁的区别就在于每次获取锁的时候,非公平锁可以先尝试获取锁再入队(这就会导致后来的线程可能直接获取到锁,相当于插队),而公平锁必须先入队再获取锁,除非队列本来就是空的。

因此,FairSync 和 NonfairSync 的 initialTryLock 逻辑相同的部分:检查当前线程是否与 exclusiveOwnerThread 相同,如果是,状态递增 1。
不同的部分:NonfairSync 直接对状态 CAS(0, 1),成功就更新 exclusiveOwnerThread 为当前线程。FairSync 先检查了队列中是否有其他线程,如果没有其他线程,才会 CAS。

如果你仔细观察上文中 require 的逻辑,你就会发现线程在入队并挂起之前至少还会自旋尝试 3 次 tryAcquire,因此 FairSync 的 tryAcquire 在对状态 CAS(0, 1) 之前还需要判断当前线程是否是头节点,确保是已经入队并且处于队首的那个节点在尝试。

ReentrantLock.tryLock(long timeout, TimeUnit unit) 的执行流程很类似:
ReentrantLock.tryLock(long timeout, TimeUnit unit) ->
sync.tryLockNanos(unit.toNanos(timeout)) -> sync.initialTryLock() --失败-->
sync.super.tryAcquireNanos(1, nanos) -> sync.tryAcquire(1)

看到这里,我想请你思考一下,tryLock() 的执行流程是怎么样的呢?

其实 tryLock() 的逻辑并不像上文中的 lock() 和 tryLockNanos(long nanos)。由于该方法的定义:直接尝试获取一次锁,返回成功或者失败。因此它根本不会涉及到出入队,也就无从谈起公平还是非公平了。也是因此,tryLock() 并不会调用抽象方法 initialTryLock,而是自己定义了与 NonfairSync.initialTryLock() 完全相同的逻辑。

Condition

很显然,Condition 功能的实现也需要依赖 AQS。ReentrantLock.newCondition 返回的就是 AQS 内部实现类 ConditionObject。
await 的几种没有超时、有超时的实现逻辑比较复杂,但是核心的部分都相同:每当调用 await,将线程封装成 ConditionNode 并加入 ConditionObject 自己维护的链表,然后释放锁,LockSupport.park() 阻塞线程。每当调用 ConditionNode.signal(),唤醒列表第一个线程,让它重新入队竞争锁。如果是 ConditionNode.signalAll(),则所有被唤醒的线程都要重新入队(然后重试到达上限被挂起,悲)。