JDK1.8 ReentrantLock相关源码
java.util.concurrent.locks.ReentrantLock内部维护了AbstractQueuedSynchronizer的实现类java.util.concurrent.locks.ReentrantLock.Sync,主要是基于AbstractQueuedSynchronizer实现的一些同步功能,
其中AbstractQueuedSynchronizer内部也是基于LockSupport.park(this);LockSupport.unpark(node.thread)的基本加锁解锁功能和大量的CAS 操作,实现数据的同步和线程的调度功能,为其他线程相关操作类提供了一个基础的工具类。
其中CAS操作系统调用代价低,原因如下(参考AI回答)
1. 硬件级原子指令支持
- CAS 操作通过 CPU 提供的单条原子指令(如 x86 的
cmpxchg
、ARM 的ldrex/strex
)直接实现比较与交换操作,无需进入内核态。 - 该指令在单个时钟周期完成内存值与预期值的比对和更新,完全在用户态执行,避免了系统调用上下文切换的开销。
2. 无锁化设计减少阻塞
- 无锁自旋:CAS 通过循环重试(自旋)处理竞争,线程始终处于运行状态,无线程挂起/唤醒操作,规避了内核调度成本。
- 对比锁机制:传统锁(如 synchronized)在竞争失败时触发线程阻塞,涉及内核态切换(如
futex
系统调用)和上下文保存/恢复,开销显著高于自旋。
3. 内核交互的极简触发
- CAS 仅在发生竞争时依赖用户态指令,不强制与内核交互。
- 仅当多核 CPU 缓存不一致时,通过 MESI 协议或总线锁定协调缓存(硬件层面完成),极少触发内核介入。
4. 性能对比数据
场景 | CAS 机制 | synchronized | 差异来源 |
---|---|---|---|
无竞争操作 | ≈10ns(用户态CAS指令) | ≈20ns(偏向锁CAS) | 67 |
单一竞争 | ≈100ns(自旋数次) | >1000ns(锁膨胀+内核阻塞) | 47 |
高并发竞争 | 自旋消耗CPU(需控制重试) | 阻塞队列调度稳定 | 412 |
java.util.concurrent.locks.ReentrantLock#lock
NonfairSync和FairSync继承Sync,分别实现了一些非公平和公平获取锁的逻辑,其中FairSync基于NonfairSync。
源码中206行可知,AQS中状态为0代表为线程占用,1代表有线程占用。所谓的锁,本质是一些数据标识。setExclusiveOwnerThread方法代表当前线程获取锁成功,设置为AQS中的独占线程。如获取锁失败,再进入209行尝试获取锁。
进入java.util.concurrent.locks.ReentrantLock.NonfairSync#tryAcquire====>java.util.concurrent.locks.ReentrantLock.Sync#nonfairTryAcquire
从131-137行可以看出新来的线程可以直接根据当前状态判断是否能拿到锁,不需要和AQS队列里的线程竞争,这就是非公平的体现。138行-144行看出如果当前线程已经是AQS中的独占线程是,再加锁时,状态会叠加,体现了可重入的特性(这段代码中当前线程是独占执行的,不需要CAS操作)。
如果nonfairTryAcquire获取失败,会先进入
java.util.concurrent.locks.AbstractQueuedSynchronizer#addWaiter
该方法主要是创建AQS双向链表中的节点,并加入链表的尾部,每个节点封装了线程对象。
再进入java.util.concurrent.locks.AbstractQueuedSynchronizer#acquireQueued
这个方法主要是循环获取锁,如获取不到则阻塞当前线程,此时当前线程已在AQS链表中,可有其他线程(链表中前一个节点)唤醒。从862-868行看出获取锁是从链表的头部节点开始的。
进入java.util.concurrent.locks.AbstractQueuedSynchronizer#shouldParkAfterFailedAcquire。可以看出,前一节点waitStatus状态必须为Node.SIGNAL,当前节点才能阻塞。因为阻塞节点的唤醒必须基于前一节点waitStatus状态。808-811行类似以lazy方式根据状态更新双向链表
java.util.concurrent.locks.ReentrantLock#unlock
java.util.concurrent.locks.ReentrantLock#unlock底层调用了java.util.concurrent.locks.AbstractQueuedSynchronizer#release方法
在java.util.concurrent.locks.ReentrantLock.Sync#tryRelease方法中,当前AQS的state字段减去参数arg,当state为0时,释放锁,唤醒其他线程。