【知识点006】公平锁为什么性能开销更大?
公平锁与非公平锁性能差异解析
公平锁(Fair Lock)和非公平锁(Nonfair Lock)是ReentrantLock的两种工作模式。
一、核心差异对比
| 特性 | 公平锁 | 非公平锁 |
|---|---|---|
| 获取锁顺序 | 严格按照线程等待顺序分配 | 允许插队(新线程可能直接抢到锁) |
| 实现复杂度 | 高(需维护等待队列) | 低(直接CAS尝试) |
| 吞吐量 | 较低 | 较高(可提升30%-50%) |
| 上下文切换 | 频繁 | 较少 |
二、性能开销大的具体原因
1. 队列维护开销
公平锁需要严格维护一个FIFO线程等待队列(CLH队列),每次锁释放时:
// 公平锁的获取逻辑(简化版)
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
// 关键区别:必须检查是否有前驱节点
if (!hasQueuedPredecessors() &&
compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
// ...处理重入逻辑
}
hasQueuedPredecessors()方法需要遍历队列检查- 非公平锁直接尝试CAS,无队列检查
2. 上下文切换频繁
graph LR
A[线程1释放锁] --> B[唤醒队列头线程2]
B --> C[线程2获取锁执行]
C --> D[线程3仍在等待]
- 公平锁必须唤醒队列中的下一个线程
- 被唤醒线程从阻塞状态恢复需要CPU上下文切换(约1-5μs)
3. CPU缓存失效
- 新请求的线程可能刚释放锁,其数据仍在CPU缓存中
- 公平锁强制切换线程会导致缓存失效(Cache Miss)
三、底层实现差异
非公平锁获取流程:
sequenceDiagram
participant Thread
participant Lock
Thread->>Lock: 直接CAS尝试获取
alt CAS成功
Lock-->>Thread: 立即获得锁
else CAS失败
Thread->>Lock: 加入队列尾部
end
公平锁获取流程:
sequenceDiagram
participant Thread
participant Lock
Thread->>Lock: 检查队列是否为空
alt 队列空且CAS成功
Lock-->>Thread: 获得锁
else 队列非空或CAS失败
Thread->>Lock: 加入队列尾部
end
正是这些额外的检查步骤和严格的顺序保证,使得公平锁在实现上需要更多的计算资源和同步操作,从而导致其性能开销大于非公平锁。在大多数高并发场景中,非公平锁是更好的默认选择。

浙公网安备 33010602011771号