公平锁与非公平锁是并发编程中两种核心的锁策略,主要区别在于锁分配的公平性机制。以下是基于底层原理、性能表现和应用场景的详细对比:
1. 定义与核心区别
-
公平锁:严格遵循 FIFO(先来先得) 原则。线程按请求锁的顺序排队,新请求的线程必须加入等待队列末尾,只有轮到其位置时才能获取锁。
特点:避免线程饥饿(Starvation),保证公平性;但性能开销较大
-
非公平锁: 允许线程插队抢占锁。新线程可直接尝试获取锁(即使等待队列中有其他线程),成功则立即执行;失败才加入队列等待。
特点:吞吐量更高,但可能导致部分线程长时间无法获取锁(饥饿现象)
2. 底层实现差异(以 Java 的 ReentrantLock 为例)
公平锁
-
核心方法tryAcquire()中调用 hasQueuedPredecessors()`,检查当前线程是否是等待队列的首位。
-
若队列非空且当前线程不在队首,则获取锁失败,线程加入队列等待。
protected final boolean tryAcquire(int acquires) { if (!hasQueuedPredecessors() && compareAndSetState(0, acquires)) { setExclusiveOwnerThread(currentThread); return true; } return false; }
非公平锁
-
直接通过 CAS(Compare-And-Swap) 尝试抢占锁,无视等待队列
final void lock() { if (compareAndSetState(0, 1)) // 尝试直接抢锁 setExclusiveOwnerThread(currentThread()); else acquire(1); // 失败后进入队列 } -
优势:减少线程切换开销,避免从运行态切换到阻塞态
3. 性能与吞吐量对比
| 指标 | 公平锁 | 非公平锁 |
|---|---|---|
| 吞吐量 | 较低(需维护队列顺序) | 更高(减少线程切换) |
| 延迟稳定性 | 更稳定(按序执行) | 波动大(插队导致不确定性) |
| 线程饥饿 | 不会发生 | 可能发生 |
实测数据:在 64 线程高并发场景下,非公平锁的吞吐率可达公平锁的 10 倍以上
4. 适用场景
公平锁适用场景
-
顺序敏感型任务:如打印任务队列、数据库事务顺序处理。
-
避免饥饿的实时系统:如金融交易系统,确保每个请求按序处理
非公平锁适用场景
-
高吞吐需求:如缓存系统(Redis)、线程池任务调度。
-
短期锁竞争:锁持有时间短时,插队能显著提升响应速度
5. Java 中的默认行为
-
ReentrantLock`:
-
默认非公平锁(new ReentrantLock())。
-
需显式指定公平性:new ReentrantLock(true)。
-
-
synchronized: 底层通过 ObjectMonitor 实现,仅支持非公平锁,无法配置为公平锁
总结:核心区别与选择建议
| 维度 | 公平锁 | 非公平锁 |
|---|---|---|
| 锁分配原则 | 严格 FIFO,先请求先获得 | 允许插队,新线程可抢占 |
| 性能优先级 | 公平性 > 性能 | 性能 > 公平性 |
| 适用场景 | 顺序敏感、避免饥饿 | 高并发、高吞吐需求 |
| 实现复杂度 | 需维护等待队列,开销大 | 实现简单,减少上下文切换 |
📌 建议:
优先选择非公平锁:在大多数高并发场景中,其性能优势显著(Java 默认策略即是证明)。
仅当业务强需求时使用公平锁:如订单处理、审计日志等严格顺序场景


2231

被折叠的 条评论
为什么被折叠?



