Java公平锁与非公平锁源码分析和核心差异
Java公平锁与非公平锁的核心区别在于线程获取锁的顺序是否遵循"先来先服务"原则。公平锁严格按照FIFO顺序分配锁资源,而非公平锁允许新请求的线程插队获取锁。 关键区别: 公平锁:检查等待队列是否有线程排队,若有则直接加入队列尾部 非公平锁:先直接尝试获取锁,失败后才加入队列 实现原理: 基于ReentrantLock和AQS实现 公平锁通过FairSync类实现,非公平锁通过
Java公平锁 vs 非公平锁
公平锁和非公平锁是排他锁(独占锁) 的两种调度策略,核心区别是线程获取锁的顺序是否遵循 “先来先服务”,ReentrantLock是 Java 中最典型的实现(通过构造方法指定公平性)。
核心定义
- 公平锁:线程获取锁的顺序严格遵循FIFO(先进先出),等待时间最长的线程优先获取锁,不会出现 “插队” 现象;
- 非公平锁:线程获取锁时不遵循 FIFO,新请求锁的线程可能 “插队” 优先获取锁(即使等待队列中有排队的线程),是 Java 中锁的默认实现(如
ReentrantLock()无参构造、synchronized)。
实现原理
- 公平锁:线程请求锁时,先检查等待队列是否有排队线程,若有则直接加入队列尾部排队;只有队列头部的线程才能竞争锁;
- 非公平锁:线程请求锁时,先直接尝试获取锁(插队),只有获取失败时,才会加入等待队列尾部排队。
适用场景
- 公平锁:对锁获取顺序有严格要求、不允许饥饿的场景(如金融交易、任务调度),牺牲性能换公平性;
- 非公平锁:绝大多数业务场景(如普通接口同步、数据缓存更新),优先保证吞吐量和性能,容忍轻微的插队。
import java.util.concurrent.locks.ReentrantLock;
public class FairUnfairLockDemo {
// 公平锁:传入true
private static final ReentrantLock FAIR_LOCK = new ReentrantLock(true);
// 非公平锁:传入false(或无参构造,默认非公平)
private static final ReentrantLock UNFAIR_LOCK = new ReentrantLock(false);
public static void main(String[] args) {
// 测试公平锁
for (int i = 0; i < 5; i++) {
new Thread(() -> {
for (int j = 0; j < 2; j++) {
FAIR_LOCK.lock();
try {
System.out.println(Thread.currentThread().getName() + " 获取公平锁");
} finally {
FAIR_LOCK.unlock();
}
}
}, "公平线程-" + i).start();
}
// 测试非公平锁
/*for (int i = 0; i < 5; i++) {
new Thread(() -> {
for (int j = 0; j < 2; j++) {
UNFAIR_LOCK.lock();
try {
System.out.println(Thread.currentThread().getName() + " 获取非公平锁");
} finally {
UNFAIR_LOCK.unlock();
}
}
}, "非公平线程-" + i).start();
}*/
}
}
公平线程-0 获取公平锁
公平线程-1 获取公平锁
公平线程-3 获取公平锁
公平线程-2 获取公平锁
公平线程-4 获取公平锁
公平线程-0 获取公平锁
公平线程-1 获取公平锁
公平线程-3 获取公平锁
公平线程-2 获取公平锁
公平线程-4 获取公平锁
非公平线程-0 获取非公平锁
非公平线程-0 获取非公平锁
非公平线程-2 获取非公平锁
非公平线程-2 获取非公平锁
非公平线程-1 获取非公平锁
非公平线程-1 获取非公平锁
非公平线程-4 获取非公平锁
非公平线程-4 获取非公平锁
非公平线程-3 获取非公平锁
非公平线程-3 获取非公平锁
Java 公平锁与非公平锁 源码分析与核心流程(基于 ReentrantLock + AQS)
Java 中公平锁与非公平锁的核心实现依托于 java.util.concurrent.locks.ReentrantLock,其底层基于AQS(抽象队列同步器,AbstractQueuedSynchronizer) 实现,二者的核心差异体现在锁的获取逻辑上,释放逻辑完全一致。
一、核心底层依赖
- ReentrantLock:对外暴露锁的操作接口(
lock()/unlock()),通过构造方法指定公平性(true= 公平锁,false= 非公平锁,无参默认非公平),内部持有一个同步器实现类 Sync(AQS 的子类); - Sync 抽象类:ReentrantLock 的内部类,继承 AQS,实现锁的可重入性和基础释放逻辑,定义抽象方法
lock()由子类实现(区分公平 / 非公平); - NonfairSync/ FairSync:Sync 的两个子类,分别实现非公平锁和公平锁的核心获取逻辑;
- AQS 核心属性:
state:volatile 修饰的 int 变量,代表锁的状态(0 = 无锁,>0 = 有线程持有锁,数值 = 重入次数);exclusiveOwnerThread:持有排他锁的当前线程(AQS 的父类 AbstractOwnableSynchronizer 中的属性);- 双向等待队列:由 Node 节点组成,存放获取锁失败的线程,实现 FIFO 排队。
二、非公平锁(NonfairSync):源码 + 核心流程
非公平锁是 ReentrantLock 的默认实现,核心特点:线程请求锁时,先直接尝试抢占锁(插队),抢占失败后再加入 AQS 等待队列排队,这是其性能高于公平锁的关键。
(1)ReentrantLock 非公平锁初始化
// ReentrantLock 无参构造(默认非公平)
public ReentrantLock() {
sync = new NonfairSync(); // 初始化非公平同步器
}
// 有参构造(指定false为非公平)
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
(2)NonfairSync 核心实现(ReentrantLock 内部类)
static final class NonfairSync extends Sync {
private static final long serialVersionUID = 7316153563782823691L;
// 非公平锁的核心获取方法:ReentrantLock.lock() 最终调用此方法
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);
}
}
(3)Sync 中的 nonfairTryAcquire 方法(非公平锁尝试获取核心,可重入)
abstract static class Sync extends AbstractQueuedSynchronizer {
// 非公平锁的尝试获取逻辑(公平锁不会调用此方法)
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState(); // 获取当前锁状态
// 情况1:锁处于无锁状态(state=0)
if (c == 0) {
// 再次 CAS 抢占(无需检查队列,直接插队)
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
// 情况2:锁已被当前线程持有(可重入逻辑)
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires; // 重入次数+1
if (nextc < 0) // 溢出保护(int 最大值+1会变为负数)
throw new Error("Maximum lock count exceeded");
setState(nextc); // 更新 state,无需 CAS(当前线程已持有锁,独占)
return true;
}
// 情况3:锁被其他线程持有,获取失败
return false;
}
}
(4)AQS 中的 acquire 方法(公共框架,失败后入队)
public final void acquire(int arg) {
// 核心逻辑:tryAcquire 失败 → 调用 addWaiter 将线程加入等待队列 → acquireQueued 阻塞等待
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt(); // 中断自我补充(防止阻塞期间中断信号丢失)
}
三、非公平锁完整获取流程(lock () 调用后)
核心口诀:先抢占 → 再重入 → 失败入队,共分 6 步:
-
线程调用
ReentrantLock.lock(),底层执行NonfairSync.lock(); -
第一次 CAS 抢占:尝试将 AQS 的
state从 0 改为 1,若成功,设置当前线程为锁持有者,获取锁完成; -
若 CAS 失败,调用 AQS 的
acquire(1)进入后续逻辑; -
acquire调用NonfairSync.tryAcquire(1),底层执行nonfairTryAcquire(1); -
nonfairTryAcquire做二次判断:-
若
state=0(前一步抢占的线程已释放锁),再次 CAS 插队抢占,成功则获取锁; -
若当前线程已是锁持有者,重入次数 + 1,更新
state,获取锁完成; -
若锁被其他线程持有,返回
false,尝试获取失败;
-
-
尝试获取失败后,通过
addWaiter将当前线程封装为排他型 Node 节点,加入 AQS 双向等待队列的尾部,再通过acquireQueued使线程进入阻塞状态,等待队列头部节点释放锁后被唤醒。
四、公平锁(FairSync):源码 + 核心流程
公平锁的核心特点:严格遵循 FIFO 先进先出,线程请求锁时,不会直接抢占,而是先检查 AQS 等待队列是否有排队的线程(队列非空则必须排队),只有队列无等待线程时,才尝试获取锁,从根本上避免 “插队”,保证公平性。
(1)FairSync 核心实现(ReentrantLock 内部类)
static final class FairSync extends Sync {
private static final long serialVersionUID = -3000897897090466540L;
// 公平锁的核心获取方法:ReentrantLock.lock() 最终调用此方法
final void lock() {
// 关键差异1:直接调用 AQS 的 acquire(1),无前置 CAS 抢占逻辑
acquire(1);
}
// 重写 AQS 的 tryAcquire 方法:公平锁的尝试获取逻辑(含可重入+队列检查)
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
// 情况1:锁处于无锁状态(state=0)
if (c == 0) {
// 关键差异2:先检查队列 → hasQueuedPredecessors(),再尝试 CAS
// hasQueuedPredecessors():判断队列是否有“等待更久的线程”(队列非空且当前线程不是队列头节点的下一个节点)
if (!hasQueuedPredecessors() &&
compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
// 情况2:锁已被当前线程持有(可重入逻辑,与非公平锁完全一致)
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
// 情况3:队列有等待线程 或 CAS 失败,获取失败
return false;
}
}
(2)AQS 中的 hasQueuedPredecessors 方法(公平锁核心,队列检查)
public final boolean hasQueuedPredecessors() {
Node t = tail; // 队列尾节点
Node h = head; // 队列头节点
Node s;
// 核心逻辑:队列非空(h != t)且 头节点的下一个节点(s)存在 且 s 不是当前线程 → 返回 true(有等待线程)
return h != t &&
((s = h.next) == null || s.thread != Thread.currentThread());
}
说明:AQS 队列的头节点是 “哨兵节点”(无实际线程,代表已释放锁的节点),真正的等待线程从 h.next 开始,因此只需检查 h.next 即可判断是否有等待更久的线程。
五、公平锁完整获取流程(lock () 调用后)
核心口诀:先查队列 → 再获取 → 失败入队,共分 5 步(与非公平锁的核心差异在第 2、3 步):
-
线程调用
ReentrantLock.lock(),底层执行FairSync.lock(); -
直接调用 AQS 的 acquire (1),无前置 CAS 抢占逻辑(杜绝插队);
-
acquire调用FairSync.tryAcquire(1),做核心判断:-
若
state=0(无锁),先调用hasQueuedPredecessors()检查队列:若队列无等待线程,才尝试 CAS 获取锁,成功则设置锁持有者;若队列有等待线程,直接返回false; -
若当前线程已是锁持有者,重入次数 + 1,更新
state,获取锁完成; -
若锁被其他线程持有,返回
false,尝试获取失败;
-
-
尝试获取失败后,与非公平锁执行完全相同的逻辑:通过
addWaiter将线程封装为排他型 Node 节点,加入 AQS 队列尾部; -
最后通过
acquireQueued使线程进入阻塞状态,等待被队列头部节点唤醒,严格按FIFO顺序获取锁。
五、公平锁与非公平锁的释放流程(完全一致)
二者的锁释放逻辑无任何差异,均由 Sync 类实现,依托 AQS 的 release 方法,核心保证可重入锁的正确释放(重入次数归 0 才真正释放锁)和唤醒队列下一个等待线程
(1)ReentrantLock.unlock () 对外接口
public void unlock() {
sync.release(1); // 调用 AQS 的 release 方法
}
(2)AQS 中的 release 方法(公共框架)
public final boolean release(int arg) {
// 第一步:尝试释放锁 → tryRelease 返回 true 表示锁已完全释放(重入次数归0)
if (tryRelease(arg)) {
Node h = head; // 获取队列头节点
// 头节点不为空 且 节点状态正常 → 唤醒队列下一个等待线程
if (h != null && h.waitStatus != 0)
unparkSuccessor(h); // 唤醒后继节点(LockSupport.unpark())
return true;
}
return false;
}
(3)Sync 中的 tryRelease 方法(实际释放逻辑,可重入)
protected final boolean tryRelease(int releases) {
int c = getState() - releases; // 重入次数-1
// 检查:只有锁持有者才能释放锁(否则抛异常,防止非法释放)
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
// 核心:重入次数归0 → 真正释放锁(清空持有者,state置0)
if (c == 0) {
free = true;
setExclusiveOwnerThread(null);
}
setState(c); // 更新state(若有重入,仅减少次数,不释放锁)
return free; // 只有完全释放时才返回true
}
- 线程调用
ReentrantLock.unlock(),底层执行sync.release(1); - 调用
tryRelease(1),将state减 1,判断当前线程是否为锁持有者(非持有者抛异常); - 若
state减至 0 → 清空锁持有者,标记为 “完全释放”,返回true;若state > 0(还有重入),仅更新state,返回false; - 若完全释放锁,获取 AQS 队列头节点,若头节点有效,调用
unparkSuccessor唤醒头节点的下一个节点(队列中第一个等待线程); - 被唤醒的线程会重新尝试获取锁(公平锁按队列顺序,非公平锁唤醒后仍会尝试插队)。
核心差异一句话总结
非公平锁在两个节点做了 “插队尝试”:① lock() 方法开头的直接 CAS 抢占;② tryAcquire 中 state=0 时的再次 CAS 抢占(无需检查队列);而公平锁全程无插队,必须先查队列再获取。
六、核心流程总结
1. 非公平锁获取核心
lock() → 直接 CAS 抢占 → 失败则 acquire(1) → tryAcquire 再次 CAS 插队(无队列检查)→ 重入则更新次数 → 失败则入队阻塞。
2. 公平锁获取核心
lock() → 直接 acquire(1) → tryAcquire 先查队列(hasQueuedPredecessors())→ 队列空则 CAS 获取 → 重入则更新次数 → 失败则入队阻塞。
3. 通用释放核心
unlock() → release(1) → tryRelease 减少重入次数 → 次数归 0 则清空持有者、state 置 0 → 唤醒队列下一个等待线程。
更多推荐



所有评论(0)