AQS 中的公平锁 vs 非公平锁如何实现?
                           
天天向上
发布: 2025-07-20 10:48:00

原创
575 人浏览过

在 Java 的 AQS(AbstractQueuedSynchronizer)中,公平锁 vs 非公平锁是控制线程获取锁顺序的两种策略。它们分别代表:

  • 公平锁(Fair Lock):线程获取锁遵循先来先服务,排队执行。
  • 非公平锁(Non-Fair Lock):线程来就尝试抢锁,可能插队,不一定先到先得。

这两种策略影响锁的获取效率、线程调度的公平性,具体由 AQS 与 ReentrantLock 的实现方式决定。


一、快速结论对比表

特性公平锁 (fair=true)非公平锁 (fair=false)
获取顺序严格按照队列 FIFO 顺序可能“插队”,只要锁空闲就抢
性能表现较差(线程频繁挂起/唤醒)更高效(减少上下文切换)
线程饥饿可能几乎没有有可能某线程长期抢不到锁
实现复杂度较高(需要判断前驱节点)简单(直接尝试 CAS)
AQS 方法重写 tryAcquire() + hasQueuedPredecessors() 判断直接 CAS 尝试更新 state
应用场景实时性高、对顺序要求强(如任务调度)大多数高并发、吞吐量优先的应用场景

二、源码视角剖析:如何实现公平/非公平

ReentrantLock 是如何指定公平性的?

ReentrantLock fairLock = new ReentrantLock(true);   // 公平锁
ReentrantLock unfairLock = new ReentrantLock(false); // 非公平锁(默认)

底层源码:

public ReentrantLock(boolean fair) {
    sync = fair ? new FairSync() : new NonfairSync();
}

它内部用两个子类来实现不同策略:

  • FairSync extends Sync
  • NonfairSync extends Sync

1. 非公平锁:NonfairSync.tryAcquire()

final boolean nonfairTryAcquire(int acquires) {
    final Thread current = Thread.currentThread();
    int c = getState();

    if (c == 0) {
        // 直接尝试获取锁,抢到就成功(不管队列有没有其他线程)
        if (compareAndSetState(0, acquires)) {
            setExclusiveOwnerThread(current);
            return true;
        }
    }
    // 可重入逻辑
    else if (current == getExclusiveOwnerThread()) {
        int nextc = c + acquires;
        setState(nextc);
        return true;
    }
    return false;
}

关键点:没有检查等待队列!锁空就抢,抢到为王。


2. 公平锁:FairSync.tryAcquire()

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;
        }
    }
    // 可重入
    else if (current == getExclusiveOwnerThread()) {
        int nextc = c + acquires;
        setState(nextc);
        return true;
    }
    return false;
}

🔍 hasQueuedPredecessors()

public final boolean hasQueuedPredecessors() {
    Node h, t, s;
    return ((h = head) != (t = tail) &&
            ((s = h.next) == null || s.thread != Thread.currentThread()));
}

关键点:当前线程只有在**自己是等待队列的头节点(排在第一)**才允许获取锁。


三、举个线程抢锁的例子对比

场景:

三个线程 A、B、C,依次请求锁。

  • 公平锁:
  A 获得锁 → A 释放 → B 获得 → B 释放 → C 获得
  • 非公平锁:
  A 获得锁 → A 释放 → C 抢先 → B 被饿死一会

四、为什么非公平锁更快?

非公平锁减少了以下开销:

  • 不检查等待队列(少了 hasQueuedPredecessors()
  • 避免线程频繁挂起/唤醒(直接尝试)
  • 减少线程上下文切换

性能差异测试(高并发场景):

锁类型吞吐量(请求数/秒)
非公平锁✅ 更高
公平锁❌ 明显下降

但注意:非公平锁存在线程饥饿风险


五、官方与源码资料


总结口诀

公平排队,保障顺序;非公平锁,抢占优先;性能优先选非公平,调度严格用公平锁。


更多详细内容请关注其他相关文章!

发表回复 0

Your email address will not be published. Required fields are marked *