ReentrantLock源码解析:公平锁与非公平锁实现
本文将基于 AQS 原理,深入剖析 ReentrantLock 中公平锁(FairSync)与非公平锁(NonfairSync)的源码实现,对比二者的核心差异,同时结合实验验证性能区别,帮大家彻底掌握这两种锁的设计逻辑与适用场景。
一、前言
在上一篇文章中,我们已经掌握了 AQS 的核心原理,明确了 ReentrantLock 是基于 AQS 实现的独占式同步工具。而 ReentrantLock 最核心的差异化特性,就是公平锁与非公平锁的可选策略。
为什么默认是非公平锁?公平锁的 “公平” 是如何通过代码实现的?非公平锁的性能优势又来自哪里?本文将基于 AQS 原理,深入剖析 ReentrantLock 中公平锁(FairSync)与非公平锁(NonfairSync)的源码实现,对比二者的核心差异,同时结合实验验证性能区别,帮大家彻底掌握这两种锁的设计逻辑与适用场景。
二、公平锁与非公平锁的核心区别
在解读源码前,我们先明确二者的本质差异:
-
公平锁:线程获取锁的顺序严格遵循FIFO(先进先出),即先进入 AQS 等待队列的线程,优先获取锁,不会出现线程饥饿,但会因频繁的线程切换带来性能损耗;
-
非公平锁:线程获取锁时,会先尝试直接抢占(CAS 修改 state),抢占失败后再进入队列排队,不保证先到先得,吞吐量更高,但可能导致部分线程长期无法获取锁(理论上存在饥饿风险,实际业务中极少出现)。
二者的核心差异点,集中在 tryAcquire 方法的实现上 —— 这也是 AQS 留给子类的核心扩展点。
三、非公平锁(NonfairSync)源码解析
ReentrantLock 默认使用非公平锁,其核心实现类为 NonfairSync ,我们从 lock() 方法开始,逐步拆解完整流程。
1. NonfairSync 的 lock () 方法
static final class NonfairSync extends Sync {
private static final long serialVersionUID = 7316153563782823691L;
// 非公平锁的核心加锁方法
final void lock() {
// 第一步:直接CAS抢占锁,将state从0改为1
if (compareAndSetState(0, 1))
// 抢占成功,设置当前线程为独占锁持有者
setExclusiveOwnerThread(Thread.currentThread());
else
// 抢占失败,调用AQS的acquire方法(进入排队流程)
acquire(1);
}
// 重写AQS的tryAcquire方法,实现非公平锁的抢占逻辑
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
}
核心逻辑拆解 :
-
CAS 直接抢占:线程调用 lock() 时,不会先检查队列,而是直接通过 compareAndSetState(0,1) 尝试修改 AQS 的 state—— 这是 “非公平” 的核心体现,新线程可能插队获取锁;
-
抢占成功:通过 setExclusiveOwnerThread 标记当前线程为锁持有者,无需进入队列;
-
抢占失败:调用 AQS 的 acquire(1) 方法,进入 “尝试获取→入队排队→阻塞” 的标准流程。
2. 核心方法:nonfairTryAcquire (int acquires)
nonfairTryAcquire 是 Sync 类的核心方法,实现了非公平锁的抢占 + 重入逻辑,代码如下:
abstract static class Sync extends AbstractQueuedSynchronizer {
// 非公平锁的tryAcquire核心逻辑
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
// 情况1:state=0,说明锁未被持有,再次尝试CAS抢占
if (c == 0) {
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
// 情况2:state>0,说明锁已被持有,判断是否是当前线程重入
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0) // 溢出保护
throw new Error("Maximum lock count exceeded");
setState(nextc); // 重入时直接修改state(无需CAS,因为当前线程已持有锁)
return true;
}
// 情况3:锁被其他线程持有,返回false,进入队列排队
return false;
}
}
核心逻辑拆解 :
-
锁未被持有(state=0):再次尝试 CAS 抢占(这是 acquire 流程中的二次抢占),保证非公平性;
-
锁已被当前线程持有:执行 重入逻辑 ,将 state 加 1(ReentrantLock 可重入的核心实现),比如线程连续调用 3 次 lock() ,state 会变为 3;
-
锁被其他线程持有:返回 false,触发 AQS 的入队逻辑。
3. 非公平锁的释放逻辑(tryRelease)
释放逻辑由 Sync 类统一实现(公平锁与非公平锁共用),核心是重入计数归零:
protected final boolean tryRelease(int releases) {
// 释放一次锁,state减1
int c = getState() - releases;
// 校验:只有锁持有者才能释放锁
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
// 当state=0时,说明锁完全释放,清空持有者
if (c == 0) {
free = true;
setExclusiveOwnerThread(null);
}
// 未归零(重入状态),仅更新state
setState(c);
return free;
}
核心要点 :只有当 state 减至 0 时,才会真正释放锁,并唤醒 AQS 队列中的后继线程。
4. 非公平锁完整流程总结

四、公平锁(FairSync)源码解析
公平锁的实现类为 FairSync ,其核心差异仅在 tryAcquire 方法,我们聚焦关键代码。
1. FairSync 的 lock () 与 tryAcquire 方法
static final class FairSync extends Sync {
private static final long serialVersionUID = -3000897897090466540L;
final void lock() {
// 公平锁直接调用AQS的acquire(1),无前置CAS抢占
acquire(1);
}
// 重写AQS的tryAcquire,实现公平锁逻辑
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
// 核心差异点:先调用hasQueuedPredecessors()检查队列
if (!hasQueuedPredecessors() &&
compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
// 重入逻辑与非公平锁一致
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
}
核心差异拆解 :
-
lock () 方法无抢占:公平锁调用 lock() 时,直接进入 AQS 的 acquire(1) 流程,不会先尝试 CAS 抢占;
-
hasQueuedPredecessors () 校验:这是公平锁的 核心保障 ,该方法会检查 AQS 队列中是否有 “等待时间更长的前驱线程”:
-
若返回 true :说明队列中有更早的线程等待,当前线程需排队,不能抢占;
-
若返回 false :说明队列为空或当前线程是队列首节点,可尝试 CAS 获取锁。
2. 关键方法:hasQueuedPredecessors ()
public final boolean hasQueuedPredecessors() {
Node t = tail; // 尾节点
Node h = head; // 头节点
Node s;
// 逻辑:头节点≠尾节点,且首节点的线程不是当前线程 → 存在前驱线程
return h != t &&
((s = h.next) == null || s.thread != Thread.currentThread());
}
逻辑说明 :AQS 队列的头节点是 “虚拟节点”,真正的等待线程从 h.next 开始。该方法通过判断 h.next 的线程是否为当前线程,确保只有队列中最靠前的线程能获取锁,实现公平性。
五、公平锁与非公平锁性能对比实验
为验证二者的性能差异,我们设计高并发场景下的吞吐量测试,对比不同锁策略的执行效率。
1. 实验代码
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.locks.ReentrantLock;
public class LockPerformanceTest {
// 并发线程数
private static final int THREAD_COUNT = 1000;
// 每个线程执行次数
private static final int LOOP_COUNT = 10000;
// 计数器
private static int count = 0;
public static void main(String[] args) throws InterruptedException {
// 测试非公平锁
testLock(new ReentrantLock(false), "非公平锁");
// 重置计数器
count = 0;
// 测试公平锁
testLock(new ReentrantLock(true), "公平锁");
}
private static void testLock(ReentrantLock lock, String lockType) throws InterruptedException {
CountDownLatch latch = new CountDownLatch(THREAD_COUNT);
long start = System.currentTimeMillis();
for (int i = 0; i < THREAD_COUNT; i++) {
new Thread(() -> {
for (int j = 0; j < LOOP_COUNT; j++) {
lock.lock();
try {
count++;
} finally {
lock.unlock();
}
}
latch.countDown();
}).start();
}
latch.await();
long end = System.currentTimeMillis();
System.out.println(lockType + "执行耗时:" + (end - start) + "ms,最终计数:" + count);
}
}
2. 实验结果(JDK 8 环境)
|
锁策略 |
线程数 |
单线程循环次数 |
总耗时(ms) |
吞吐量(次 /ms) |
|---|---|---|---|---|
|
非公平锁 |
1000 |
10000 |
856 |
11682 |
|
公平锁 |
1000 |
10000 |
1289 |
7758 |
3. 结果分析
-
非公平锁的吞吐量比公平锁高40% 以上,原因是减少了线程的排队与唤醒次数,降低了上下文切换开销;
-
公平锁因严格的队列顺序,需要频繁唤醒队列线程,导致性能损耗,但保证了线程执行的有序性。
六、公平锁与非公平锁的适用场景
1.优先使用非公平锁的场景
-
高并发、高吞吐量的核心业务(如电商订单处理、库存扣减);
-
锁持有时间短、线程竞争激烈的场景;
-
可接受 “偶尔插队”,以性能优先的场景。
2.优先使用公平锁的场景
-
对执行顺序有严格要求的业务(如日志写入、任务调度);
-
锁持有时间长、线程竞争不激烈的场景(此时性能损耗可忽略);
-
需避免线程饥饿的敏感业务(如金融交易)。
七、总结
本文深入解析了 ReentrantLock 公平锁与非公平锁的源码实现,核心要点总结如下:
-
二者的核心差异在 tryAcquire 方法,公平锁通过 hasQueuedPredecessors() 保证排队顺序,非公平锁通过前置 CAS 实现抢占;
-
非公平锁默认且性能更优,是绝大多数业务的首选;公平锁保证有序性,但存在性能损耗;
-
ReentrantLock 的可重入性通过 state 计数器实现,释放锁时需保证 unlock() 次数与 lock() 一致。
更多推荐



所有评论(0)