
公平锁的语义保证的是“等待时间最长的线程优先获取锁”,但若测试未确保线程按序进入等待队列,就无法验证其公平性;本教程详解该常见误判根源,并提供基于队列长度同步的可靠测试方案。
公平锁的语义保证的是“等待时间最长的线程优先获取锁”,但若测试未确保线程按序进入等待队列,就无法验证其公平性;本教程详解该常见误判根源,并提供基于队列长度同步的可靠测试方案。
在 Java 并发编程中,ReentrantLock(true) 声明为公平锁,其核心契约是:当多个线程竞争锁时,锁将按线程进入同步队列(AQS CLH 队列)的先后顺序授予——即 FIFO 公平策略。然而,许多开发者误以为“提交任务的顺序 = 尝试加锁的顺序 = 实际入队顺序”,从而设计出看似合理、实则不可靠的公平性测试,正如问题中所示。
? 问题根源:提交顺序 ≠ 入队顺序
原测试中,主线程依次 submit() 5 个加锁任务,但 ThreadPoolExecutor 的线程调度、JVM 指令重排、操作系统线程唤醒时机等不确定性,导致各任务实际执行 lock.lock() 的时刻高度不可控。即使任务 1 先提交,它可能因线程尚未启动或上下文切换延迟,反而比任务 3 更晚调用 lock.lock() —— 此时它将排在任务 3 之后入队,破坏了预期的排队序。因此,sharedState.get() 偶然等于 5 属于巧合,而非公平性保障。
更关键的是:公平锁只对已进入 AQS 同步队列的线程排序,不保证“谁先执行 lock() 谁先入队”。若线程在 lock() 调用前被阻塞(如刚被调度)、或存在微秒级竞争窗口,就可能错失“第一个入队者”身份。
✅ 正确验证方式:显式同步入队顺序
要严谨验证公平性,必须确保线程严格按编号顺序完成入队,再统一释放锁触发竞争。推荐使用 lock.getQueueLength() 作为同步信号——该值实时反映当前等待队列中的线程数,且是 final 字段的原子读取,无需额外同步:
在 Java 中初始化和管理阿里云 SDK客户端。包括单例模式、线程安全、endpoint 与 region 配置、VPC 终端节点、同步与异步等。
立即学习“Java免费学习笔记(深入)”;
List<Future<Integer>> futures = IntStream.rangeClosed(1, waitingThreadsCount)
.mapToObj(j -> {
Future<Integer> future = executor.submit(() -> {
try {
lock.lock(); // 阻塞直至获取锁
return sharedState.updateAndGet(k -> j);
} finally {
lock.unlock();
}
});
// 等待恰好 j 个线程进入等待队列(含当前线程)
await().atMost(200, TimeUnit.MILLISECONDS)
.until(() -> lock.getQueueLength() == j);
return future;
})
.toList();?
await().until(() -> lock.getQueueLength() == j)的含义是:当第j个任务提交后,我们断言此时队列中已有j个等待线程。这隐含要求前j−1个任务已成功调用lock.lock()并阻塞(即入队),而第j个任务也已完成lock.lock()调用(正在入队或已入队)。这是目前最轻量、最可靠的入队顺序同步手段。
⚠️ 注意事项与最佳实践
-
避免
Thread.sleep()替代显式同步:sleep无法应对 CPU 负载波动,极易导致假失败或假通过。 -
getQueueLength()是估算值:文档注明其返回“estimate”,但在所有 JDK 版本中,它对公平锁的队列长度反馈是强一致且及时的,足以支撑测试逻辑。 -
关闭线程池需超时防护:原代码
executor.shutdown()后未等待终止,应补充awaitTermination(),否则可能遗漏未完成任务。 - 公平锁性能代价真实存在:测试中观察到的“50% 失败率”恰恰反映了非公平锁在高并发下的优势——它允许新线程插队(barging),减少上下文切换。公平锁仅在可预测调度延迟场景下才有意义(如实时系统、调试验证)。
-
不公平锁的验证需反向逻辑:要证明非公平性,不能仅依赖“结果不等于 5”,而应统计多次运行中
sharedState.get()的分布——若显著偏离均匀分布(如频繁出现1或3),才说明存在插队行为。
✅ 最终结论
公平 ReentrantLock 的语义从未被打破,出问题的是测试模型本身。真正可靠的并发测试必须尊重 JVM 内存模型与线程调度的不确定性,用可观测的同步点(如 getQueueLength())替代脆弱的时序假设。上述改进后的测试能在 100% 执行中稳定通过,成为验证锁公平性的黄金标准。

















