导读:本期聚焦于小伙伴创作的《怎么利用 ReentrantLock 的 FairSync 源码理解 AQS 队列对等待线程的唤醒策略》,敬请观看详情。为什么公平锁在释放时要严格按队列顺序唤醒下一个等待者?从 ReentrantLock 的 FairSync 实现切入,可以看到 AQS 同步队列以 CLH 变体为基础,头节点释放锁后仅唤醒后继首节点。这种策略避免了线程饥饿,却也带来上下文切换成本。通过分析 tryAcquire 与 release 方法,能弄清 AQS 如何用 state 变量和等待队列配合,在公平模式下保证先到先得。理解这套唤醒逻辑,有助于在并发场景中正确选型与排查死锁。

在 Java 并发包中,ReentrantLock 的公平锁 FairSync 是理解 AbstractQueuedSynchronizer(简称 AQS)线程唤醒机制的典型入口。AQS 内部维护了一个基于 CLH 队列改良的双向等待队列,通过控制同步状态 state 与节点出队顺序,决定哪个线程可以获得锁。FairSync 在加锁时强制排队,在释放锁时严格按照队列顺序唤醒后继节点,这与非公平锁的抢占式唤醒形成鲜明对比。

怎么利用 ReentrantLock 的 FairSync 源码理解 AQS 队列对等待线程的唤醒策略

一、FairSync 的基本结构与加锁逻辑

FairSync 继承自 Sync,而 Sync 又继承自 AbstractQueuedSynchronizer。它的核心在于重写了 tryAcquire 方法,在尝试获取锁之前先判断队列中是否有前驱节点在等待。如果有,当前线程就必须进入队列排队,而不能直接抢锁。

下面的代码展示了 FairSync 中 tryAcquire 的典型实现逻辑。通过 hasQueuedPredecessors 判断是否需要排队,这是公平性的关键所在。只有队列为空或当前线程就是头节点的后继时,才会尝试用 CAS 修改 state。

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;
        if (nextc < 0)
            throw new Error("Maximum lock count exceeded");
        setState(nextc);
        return true;
    }
    return false;
}

从这段代码可以看出,FairSync 的加锁策略非常直观:只要前面有人排队,新来的线程就乖乖去队列尾部等待。这种策略保证了先到先得,但也意味着即使是空闲瞬间,线程也要走一遍排队流程,吞吐量略低于非公平模式。

hasQueuedPredecessors 方法会检查头节点是否就是当前线程所在节点,或者队列中是否已有有效等待节点。它的存在让 AQS 的唤醒策略从根源上避免了后来线程插队的可能,为后续释放锁时的顺序唤醒打下基础。

二、AQS 队列中节点的等待与唤醒关系

AQS 的等待队列是一个带有头尾指针的双向链表,每个节点保存了等待线程、等待状态 waitStatus 以及前后指针。当一个线程获取锁失败时,会被包装成节点通过 addWaiter 方法追加到队尾,并通过 LockSupport.park 挂起。

在公平锁场景下,头节点代表当前持有锁的线程(或刚释放锁的虚拟头),真正的第一个等待者位于头节点的 next。释放锁时,AQS 只会唤醒这个 next 节点对应的线程,而不是随机或广播式唤醒,这就是所谓的“队列对等待线程的唤醒策略”。

private void unparkSuccessor(Node node) {
    int ws = node.waitStatus;
    if (ws < 0)
        compareAndSetWaitStatus(node, ws, 0);
    Node s = node.next;
    if (s == null || s.waitStatus > 0) {
        s = null;
        for (Node t = tail; t != null && t != node; t = t.prev)
            if (t.waitStatus <= 0)
                s = t;
    }
    if (s != null)
        LockSupport.unpark(s.thread);
}

上面的 unparkSuccessor 是 AQS 释放锁时唤醒后继的核心方法。它首先清理当前节点状态,然后从 next 开始寻找一个状态合法的节点进行 unpark。在 FairSync 的正常流程中,next 通常就是队列中第一个有效等待线程,因此唤醒具有严格的顺序性。

这种策略的好处是不会出现线程饥饿,每个排队的线程都能按进入队列的顺序被唤醒。缺点是如果头节点释放后 next 节点所在线程被操作系统调度延迟,后续节点也只能干等,系统整体的并发响应会偏保守。

三、从 release 方法看公平唤醒的完整流程

ReentrantLock 的 unlock 方法会调用 Sync 的 release,而 release 又调用 AQS 的模板方法。只有 tryRelease 成功将 state 归零后,才会触发 unparkSuccessor,完成一次公平的唤醒传递。

下面的代码简化展示了 FairSync 释放锁的调用链。注意 tryRelease 并不区分公平或非公平,真正决定唤醒顺序的是 AQS 队列结构和 unparkSuccessor 的取值逻辑。

public void unlock() {
    sync.release(1);
}

protected final boolean tryRelease(int releases) {
    int c = getState() - releases;
    if (Thread.currentThread() != getExclusiveOwnerThread())
        throw new IllegalMonitorStateException();
    boolean free = false;
    if (c == 0) {
        free = true;
        setExclusiveOwnerThread(null);
    }
    setState(c);
    return free;
}

当 state 变为 0,release 方法会拿头节点调用 unparkSuccessor。此时头节点一般是刚释放锁的线程节点(在 AQS 中释放后头节点会被后继替换),它的 next 就是下一个该被唤醒的公平等待者。线程被唤醒后,会再次执行 tryAcquire,由于此时队列里没有比它更靠前的节点,它能够顺利拿到锁。

通过这段流程可以确认:AQS 对等待线程的唤醒策略在公平模式下表现为严格的先进先出。它不像条件队列那样可能唤醒多个线程,也不像非公平锁那样允许新线程在释放瞬间插队,而是将唤醒对象精确限定为队列首部有效节点。

四、公平唤醒策略的优缺点与适用场景

从 FairSync 源码可以总结出,AQS 的顺序唤醒策略以等待队列为唯一仲裁者,避免了资源分配的不确定性。在需要严格避免线程饥饿、且锁持有时间较短的业务中,公平锁能让调用方获得可预期的延迟表现。

但在高并发且锁竞争激烈的场景里,公平锁由于每次都要排队和唤醒,会导致更多的上下文切换。此时非公平锁的抢占机制往往能提升吞吐量。理解 FairSync 与 AQS 的协作方式,能帮助开发者在写并发组件时正确权衡,也能在排查线程堆积问题时快速定位是否因公平排队引起。

// 使用示例:公平锁的创建与释放
ReentrantLock fairLock = new ReentrantLock(true);
fairLock.lock();
try {
    // 临界区操作
} finally {
    fairLock.unlock();
}

上面这段示例虽然简单,但背后隐藏的正是 AQS 队列对等待线程的唤醒契约。只要在代码中始终使用 try-finally 保证 unlock 执行,FairSync 就能依靠 AQS 的节点唤醒规则,把锁从当前线程平稳交给队列中的下一位等待者。

总的来说,借助 ReentrantLock 的 FairSync 源码,我们能够把 AQS 中抽象的队列与状态机制映射到具体的方法调用上。看清 tryAcquire 的排队判断与 unparkSuccessor 的定点唤醒,也就真正明白了 AQS 是如何管理等待线程的生命周期与执行顺序的。

ReentrantLockFairSyncAbstractQueuedSynchronizer修改时间:2026-08-08 02:12:36

免责声明:​ 已尽一切努力确保本网站所含信息的准确性。网站内容多为原创整理与精心编撰,观点力求客观中立。本站旨在免费分享,内容仅供个人学习、研究或参考使用。若引用了第三方作品,版权归原作者所有。如内容涉及您的权益,请联系我们处理。
内容垂直聚焦
专注技术核心技术栏目,确保每篇文章深度聚焦于实用技能。从代码技巧到架构设计,为用户提供无干扰的纯技术知识沉淀,精准满足专业提升需求。
知识结构清晰
覆盖从开发到部署的全链路。AI、前端、编程、数据库、服务器、建站、系统层层递进,构建清晰学习路径,帮助用户系统化掌握开发与运维所需的核心技术。
深度技术解析
拒绝泛泛而谈,深入技术细节与实践难点。无论是数据库优化还是服务器配置,均结合真实场景与代码示例进行剖析,致力于提供可直接应用于工作的解决方案。
专业领域覆盖
精准对应开发生命周期。从前端界面到后端编程,从数据库操作到服务器运维,形成完整闭环,一站式满足全栈工程师和运维人员的技术需求。
即学即用高效
内容强调实操性,步骤清晰、代码完整。用户可根据教程直接复现和应用于自身项目,显著缩短从学习到实践的距离,快速解决开发中的具体问题。
持续更新保障
专注既定技术方向进行长期、稳定的内容输出。确保各栏目技术文章持续更新迭代,紧跟主流技术发展趋势,为用户提供经久不衰的学习价值。