AQS 是什么?

AQS 是什么?

java.util.concurrent.locks.AbstractQueuedSynchronizer,简称 AQS,是 JUC 同步器的底座。像 ReentrantLockSemaphoreCountDownLatchReentrantReadWriteLockStampedLock(部分)等,都是基于 AQS 搭建的。

AQS 做两件事:

  1. 管理一个原子状态位 statevolatile int),用 CAS 修改;

  2. 维护一个CLH 同步队列(双向链表)来排队阻塞/唤醒获取同步状态失败的线程(LockSupport.park/unpark)。

你只需要继承 AQS并覆写少数模板方法(如 tryAcquire/tryRelease 等),排队、阻塞、唤醒、公平/非公平、可中断、超时……AQS 都帮你处理好了。


核心概念一览

  • state:同步状态(通常 0=空闲,>0=已占用/剩余许可数)。

  • 独占模式(exclusive):一次只允许一个线程持有(如 ReentrantLock)。

  • 共享模式(shared):可同时允许多个线程持有(如 SemaphoreCountDownLatch 的“门闩剩余数”)。

  • CLH 队列:失败的获取者入队,头节点负责唤醒后继。

  • 可重入:独占模式下,持有者再次获取会让 state++(如 ReentrantLock)。

  • Condition:基于 AQS 提供的 ConditionObject,实现“在锁内等待/通知”。


获取/释放的大致流程(简化)

以独占为例(acquire):

  1. 调用方先走子类覆写的 tryAcquire(arg)能拿就拿(CAS 改 state,设置独占线程)。

  2. 拿不到则入队(尾插 CLH 队列)、阻塞park)。

  3. 被前驱唤醒后重试获取;成功后设为队头(“哨兵”),以便继续唤醒下一位。

释放(release):

  1. 调子类 tryRelease(arg):把 state 调整(可能到 0)。

  2. 如果完全释放(state==0),就唤醒后继unpark)。

共享模式(acquireShared/releaseShared):

  • tryAcquireShared(arg) 返回剩余许可(>=0 表示成功且可能还有余量继续唤醒别人;<0 表示失败需排队)。

  • releaseShared 成功后会级联唤醒后继,直到余量耗尽或队列空。

可中断与超时:AQS 提供 acquireInterruptiblytryAcquireNanos 等变体,内部处理打断与超时逻辑。


公平 vs 非公平

  • 公平:按队列先来先服务(入队后排在前面的先获取)。如 new ReentrantLock(true)

  • 非公平:允许新来的线程插队(直接先 CAS 一把),吞吐高、抖动大。默认的 ReentrantLock 非公平。


和常见同步器的映射

  • ReentrantLock:独占模式(可重入),state 表示重入次数;tryAcquire 检查持有者/CAS;tryRelease 递减到 0 时释放。

  • Semaphore:共享模式,state 表示剩余许可tryAcquireShared 用 CAS 扣减,>=0 成功。

  • CountDownLatch:共享模式,state计数await 共享获取(计数>0 则排队),countDown 共享释放(到 0 唤醒所有等待者)。

  • ReentrantReadWriteLock:读共享/写独占;state 高低位编码读写计数并发。


Condition 怎么来的?

AbstractQueuedSynchronizer 内置 ConditionObject

  • await():把当前线程从同步队列转移到条件队列并释放锁 → 阻塞等待信号。

  • signal():把条件队列里的节点转回同步队列 → 等待被重新竞争锁。

  • 只有持锁线程才能 await/signal,这保证了条件变量的正确使用时序。


手写一个最小不可重入互斥锁(示例)

import java.util.concurrent.locks.AbstractQueuedSynchronizer;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.TimeUnit;

public class SimpleMutex implements Lock {
    private static class Sync extends AbstractQueuedSynchronizer {
        // state: 0=free, 1=held
        @Override
        protected boolean tryAcquire(int acquires) {
            // 不可重入:当前没有人持有时才允许 CAS 抢占
            if (compareAndSetState(0, 1)) {
                setExclusiveOwnerThread(Thread.currentThread());
                return true;
            }
            return false;
        }
        @Override
        protected boolean tryRelease(int releases) {
            if (getState() == 0 || getExclusiveOwnerThread() != Thread.currentThread())
                throw new IllegalMonitorStateException();
            setExclusiveOwnerThread(null);
            setState(0);
            return true; // 唤醒后继
        }
        @Override
        protected boolean isHeldExclusively() {
            return getState() == 1 && getExclusiveOwnerThread() == Thread.currentThread();
        }
    }

    private final Sync sync = new Sync();

    @Override public void lock()                   { sync.acquire(1); }
    @Override public void lockInterruptibly() throws InterruptedException { sync.acquireInterruptibly(1); }
    @Override public boolean tryLock()             { return sync.tryAcquire(1); }
    @Override public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
        return sync.tryAcquireNanos(1, unit.toNanos(time));
    }
    @Override public void unlock()                 { sync.release(1); }
    @Override public java.util.concurrent.locks.Condition newCondition() {
        return sync.newConditionObject();
    }
}

说明:我们只覆写了 tryAcquire/tryRelease/isHeldExclusively,其余的排队/阻塞/唤醒/中断/超时全部由 AQS 兜底。


设计细节与性能点

  • 阻塞策略:失败后不会疯狂自旋,AQS 在合适节点调用 park(),被前驱或释放唤醒后再竞争,兼顾性能与公平。

  • 虚假唤醒:按经典并发模式,使用者应在 while 循环里检查条件(AQS 的 Condition 实现已遵循该约定)。

  • 可重入:自行在 tryAcquire 中检查“当前线程是否持有”,是则 state++;释放时 state-- 到 0 才真正释放。

  • 中断/超时:AQS 已提供对应 API 的模板逻辑,覆写只管状态语义。


何时自己写一个 AQS 同步器?

  • 你需要自定义并发语义(比如“只允许 N 个并发 + 每个线程最多重入 M 次”这类组合规则);

  • 现有的 Lock/Semaphore 无法直接覆盖你的场景;

  • 你希望享受“成熟的排队/阻塞/唤醒/中断/超时/条件队列”但只关心状态判定

只要明确:状态怎么表示(state)何时算获取成功/释放成功(tryAcquire/tryRelease),剩下交给 AQS。


常见易错点

  • 忘记设置/清除独占线程:独占模式下需要 setExclusiveOwnerThread/null

  • tryRelease 返回值错:返回 true 才会唤醒后继;如果还有重入没释放干净要返回 false

  • 共享模式返回值语义tryAcquireShared 返回 ≥0 表示成功(且可能仍有剩余许可),<0 表示失败

  • Condition 必须在持锁时 await/signal:否则会 IllegalMonitorStateException

  • 自定义同步器的可见性statevolatile,但对其他共享字段的发布要遵循 JMM(在成功获取/释放的路径做好可见性保障)。


速记卡(面试 10 秒版)

AQS = CAS 管状态 + CLH 队列管等待
覆写 tryAcquire/tryRelease(独占)或 tryAcquireShared/tryReleaseShared(共享)就能做出自己的同步器。
ReentrantLock/Semaphore/CountDownLatch/RWLock 都是它的“皮肤”。
Condition 由 ConditionObject 提供,await/signal 依赖持锁语义。

posted on 2025-11-10 14:38  滚动的蛋  阅读(0)  评论(0)    收藏  举报

导航