【知识点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

正是这些额外的检查步骤和严格的顺序保证,使得公平锁在实现上需要更多的计算资源和同步操作,从而导致其性能开销大于非公平锁。在大多数高并发场景中,非公平锁是更好的默认选择。

posted @ 2025-07-18 11:23  兔麻吕  阅读(31)  评论(0)    收藏  举报