跳转至

AQS 默认非公平加锁策略

一、公平 vs 非公平

  • 公平锁:按队列顺序,先到先得。
  • 非公平锁:新线程可以直接抢锁,不管队列里有没有人等。

ReentrantLock 默认非公平

二、非公平 lock()

final void lock() {
    if (compareAndSetState(0, 1))       // 直接抢!
        setExclusiveOwnerThread(Thread.currentThread());
    else
        acquire(1);
}

公平锁不会在开头 CAS,而是直接进 acquire:

final void lock() {
    acquire(1);   // 直接 tryAcquire,tryAcquire 里检查 hasQueuedPredecessors
}

三、非公平的 tryAcquire

final boolean nonfairTryAcquire(int acquires) {
    Thread current = Thread.currentThread();
    int c = getState();
    if (c == 0) {
        if (compareAndSetState(0, acquires)) {
            setExclusiveOwnerThread(current);
            return true;
        }
    } else if (current == getExclusiveOwnerThread()) {
        setState(c + acquires);   // 重入
        return true;
    }
    return false;
}

四、为什么非公平性能好

假设线程 A 持有锁,线程 B 在队列里等,线程 C 新来:

  • 公平:C 必须等 B 拿到锁、用完、释放,才能拿。B 从挂起到唤醒要切换线程,有延迟。
  • 非公平:C 在 B 还没完全唤醒时就抢到了锁,减少了线程切换开销。

非公平可能让队列里的线程"饿死",但吞吐更高。

五、ReentrantLock 选择

new ReentrantLock();              // 非公平(默认)
new ReentrantLock(true);          // 公平

六、源码对比

非公平 公平
lock() 先 CAS 抢 直接 acquire
tryAcquire 不检查队列 检查 hasQueuedPredecessors
性能
公平性 可能饥饿 FIFO

面试要点

  • 非公平默认:减少线程挂起/唤醒的开销。
  • 公平锁保证不饥饿,但吞吐低。
  • 大多数场景用非公平。