AQS的一些思考

AQS是怎么实现CountDown

AQS 为 CountDownLatch 提供了核心的 状态管理(state 作为计数器) 和 线程排队 / 唤醒机制(CLH 队列):

  • 通过 tryAcquireShared 检查计数器是否为 0,决定线程是否需要等待;
  • 通过 tryReleaseShared 原子递减计数器,当计数器归零时唤醒所有等待线程;

共享模式确保多个等待线程能同时被唤醒,符合 CountDownLatch 的多线程等待场景。

AQS的独占锁和共享锁

两种锁的核心差异在于 “同一时间允许多少线程持有锁”

  1. 独占锁的实现逻辑
    独占锁的核心是 “一次只能有一个线程成功获取锁”,AQS 通过以下方法支持:
  • 获取锁:acquire(int arg)
    该方法会调用子类重写的 tryAcquire(int arg),尝试独占式获取锁:
    若 tryAcquire 返回 true(获取成功),则线程继续执行;
    若返回 false(获取失败),则当前线程被包装为 EXCLUSIVE 类型的 Node 加入 CLH 等待队列,然后通过 LockSupport.park() 阻塞。
    tryAcquire 的逻辑由子类实现(如 ReentrantLock),通常基于 AQS 的 state 变量判断:例如 state=0 表示未锁定,线程通过 CAS 将 state 改为 1 表示获取锁;若 state>0 且持有线程是当前线程,则递增 state(支持重入)。
  • 释放锁:release(int arg)
    该方法会调用子类重写的 tryRelease(int arg),尝试释放锁:
    若 tryRelease 返回 true(释放成功,如 state 减为 0),则 AQS 会从等待队列中唤醒 一个 阻塞的 EXCLUSIVE 节点(线程),被唤醒的线程会再次尝试 tryAcquire 获取锁。
    例如 ReentrantLock 的 tryRelease 会递减 state,当 state=0 时表示完全释放锁,此时唤醒等待队列中的下一个线程。
  1. 共享锁的实现逻辑
    共享锁的核心是 “多个线程可同时获取锁”,AQS 通过以下方法支持:
  • 获取锁:acquireShared(int arg)
    该方法会调用子类重写的 tryAcquireShared(int arg),尝试共享式获取锁:
    若返回值 ≥0(表示获取成功,返回值可能代表剩余可用资源数),则线程继续执行;
    若返回值 <0(表示获取失败),则当前线程被包装为 SHARED 类型的 Node 加入 CLH 等待队列,然后通过 LockSupport.park() 阻塞。
    例如 Semaphore(信号量)的 tryAcquireShared 会检查剩余许可数(state 表示许可数):若 state ≥ arg,则通过 CAS 减少 state 并返回剩余许可数(≥0);否则返回 -1 表示失败。
  • 释放锁:releaseShared(int arg)
    该方法会调用子类重写的 tryReleaseShared(int arg),尝试释放锁:
    若 tryReleaseShared 返回 true(释放成功,如 state 增加后满足唤醒条件),则 AQS 会从等待队列中唤醒 所有连续的 SHARED 节点(线程),被唤醒的线程会再次尝试 tryAcquireShared,且可能继续唤醒后续的共享节点(“唤醒传播”)。
    例如 CountDownLatch 的 tryReleaseShared 会递减 state,当 state=0 时返回 true,触发唤醒所有等待的 SHARED 线程;Semaphore 释放许可时会增加 state,若此时有等待线程,则唤醒它们。
  1. 等待队列的节点差异
    AQS 的 CLH 等待队列中,节点(Node)有两种类型标识:
  • Node.EXCLUSIVE:独占模式节点,对应独占锁的等待线程;
  • Node.SHARED:共享模式节点,对应共享锁的等待线程。

这种差异直接影响唤醒逻辑:

  • 独占锁释放时,仅唤醒队列中第一个 EXCLUSIVE 节点(因为一次只能有一个线程获取);
  • 共享锁释放时,会唤醒第一个 SHARED 节点,且该节点被唤醒后,会继续唤醒后续的 SHARED 节点(因为多个线程可同时获取),形成 “唤醒链”。
posted @ 2025-09-14 00:05  柠檬水请加冰  阅读(4)  评论(0)    收藏  举报