AbstractQueuedSynchronizer (AQS)

AQS 是 java.util.concurrent 包中构建锁和同步器的核心框架,理解其原理对于掌握 Java 并发工具至关重要。

AQS 的核心概念回顾:

  • 同步状态 (State): 一个由 volatile int 类型的变量表示的共享资源的状态。子类通过 CAS 操作来管理这个状态。
  • FIFO 等待队列 (CLH 变体): 当线程获取同步状态失败时,会被封装成节点(Node)加入到队列尾部并阻塞。当状态释放时,按照 FIFO 原则唤醒队列中的线程。

AQS 的内部结构:

AQS 内部维护着:

  1. volatile int state (同步状态): 所有同步逻辑的核心。不同的同步器(如 ReentrantLockSemaphore)会赋予 state 不同的含义。
  2. transient volatile Node head (头节点): 指向等待队列的头部。头节点通常是一个“虚节点”,不关联任何实际等待的线程。
  3. transient volatile Node tail (尾节点): 指向等待队列的尾部。新的等待线程会被添加到尾部。

AQS 的等待队列是一个虚拟的双向链表,它不是通过显式的 next 指针在所有节点间直接连接,而是通过每个节点的 prevnextWaiter 字段以及与前驱节点的隐式连接来维护等待关系。

AQS 的同步模式:

AQS 支持两种主要的同步模式,子类可以选择实现一种或两种:

  1. 独占模式 (Exclusive): 同一时刻只允许一个线程持有同步状态。ReentrantLock 是独占锁的典型实现。

    • 通过 tryAcquire(int arg) 尝试获取独占锁。
    • 通过 tryRelease(int arg) 尝试释放独占锁。
    • isHeldExclusively() 判断当前线程是否持有独占锁。
  2. 共享模式 (Shared): 同一时刻允许多个线程持有同步状态。SemaphoreCountDownLatch 是共享锁的典型实现。ReentrantReadWriteLock 的读锁也是共享的。

    • 通过 tryAcquireShared(int arg) 尝试获取共享锁,返回值大于等于 0 表示成功。
    • 通过 tryReleaseShared(int arg) 尝试释放共享锁,成功返回 true

AQS 的核心操作流程:

无论是独占模式还是共享模式,AQS 的核心操作都围绕着获取和释放同步状态展开,并涉及到线程的阻塞和唤醒以及队列的管理。

1. 获取同步状态 (acquire(int arg) - 独占模式):

  1. 尝试直接获取同步状态 (tryAcquire(arg))。如果成功,则当前线程成为持有锁的线程,方法返回。
  2. 如果获取失败,则将当前线程封装成一个 Node 并添加到等待队列的尾部 (addWaiter(Node mode)mode 通常为 Node.EXCLUSIVE)。
  3. 当前线程进入阻塞状态 (parkAndUnpark(Node node)),直到被前驱节点释放锁后唤醒。
  4. 被唤醒后,再次尝试获取同步状态 (tryAcquire(arg))。如果仍然失败,则继续阻塞。
  5. 在阻塞和唤醒的过程中,节点会根据前驱节点的状态来判断是否需要阻塞,以及在释放锁后是否需要唤醒后继节点。

2. 释放同步状态 (release(int arg) - 独占模式):

  1. 尝试释放同步状态 (tryRelease(arg))。如果释放成功,则检查等待队列中是否有需要唤醒的后继节点。
  2. 如果存在后继节点且其状态表示需要被唤醒 (Node.SIGNAL),则唤醒该后继节点 (unparkSuccessor(Node node),通常唤醒头节点的后继节点)。

3. 获取共享同步状态 (acquireShared(int arg) - 共享模式):

  1. 尝试获取共享同步状态 (tryAcquireShared(arg))。如果返回值大于等于 0,表示获取成功,方法返回。
  2. 如果获取失败,则将当前线程封装成一个 Node 并以共享模式 (Node.SHARED) 添加到等待队列的尾部。
  3. 当前线程进入阻塞状态,直到被其他持有共享锁的线程释放后唤醒。
  4. 被唤醒后,再次尝试获取共享同步状态 (tryAcquireShared(arg))。如果仍然失败,则继续阻塞。
  5. 共享锁的唤醒可能会涉及到信号传播 (signal propagation),即一个共享锁的释放可能会唤醒队列中的多个后继节点,直到没有更多的线程可以获取到共享锁为止。

4. 释放共享同步状态 (releaseShared(int arg) - 共享模式):

  1. 尝试释放共享同步状态 (tryReleaseShared(arg))。如果释放成功,则需要唤醒等待队列中处于共享模式的后继节点,这些节点可能也能够获取到共享锁(通过信号传播)。

AQS 队列的特点 (CLH 队列的变体):

  • 虚拟双向链表: 虽然没有显式的 next 指针直接连接所有等待节点,但每个节点通过 prev 指向其前驱节点,并且头节点和尾节点维护着队列的边界。节点间的唤醒和状态传递依赖于这种隐式的连接。
  • head 和 tail 指针: head 指向一个通常不持有实际线程的虚节点,而 tail 指向队列中最后一个等待的节点。
  • Node 结构: 每个 Node 包含:
    • waitStatus: 节点的等待状态,例如 SIGNAL (需要被唤醒)、CONDITION (在条件队列中等待)、PROPAGATE (需要向后传播唤醒信号) 等。
    • prev: 指向前驱节点。
    • nextWaiter: 指向下一个等待相同条件或相同模式(独占/共享)的节点。
    • thread: 关联的等待线程。
  • CAS 操作: AQS 大量使用了 Compare-and-Swap (CAS) 原子操作来安全地更新 stateheadtail 等共享变量,保证并发环境下的线程安全。

AQS 的重要方法 (供子类实现):

子类需要根据自身的同步语义实现以下一个或多个方法:

  • tryAcquire(int arg)
  • tryRelease(int arg)
  • tryAcquireShared(int arg)
  • tryReleaseShared(int arg)
  • isHeldExclusively()

AQS 的模板方法 (供子类调用):

AQS 提供了一系列 final 修饰的模板方法,子类可以直接调用来实现同步逻辑,例如:

  • acquire(int arg)
  • acquireInterruptibly(int arg) (支持中断)
  • tryAcquireNanos(int arg, long nanosTimeout) (支持超时)
  • release(int arg)
  • acquireShared(int arg)
  • acquireSharedInterruptibly(int arg)
  • tryAcquireSharedNanos(int arg, long nanosTimeout)
  • releaseShared(int arg)

AQS 与 ConditionObject:

AQS 还提供了创建 ConditionObject 的能力。ConditionObject 可以与一个独占锁关联,提供了 wait()signal()signalAll() 等方法,用于实现更灵活的线程等待和通知机制。ConditionObject 内部维护着一个独立的等待队列。

总结 AQS 的核心要点:

  • AQS 是构建同步器的基石,提供了一个统一的框架。
  • 它通过维护一个同步状态 (state) 和一个 FIFO 等待队列来管理并发访问。
  • 支持独占和共享两种同步模式。
  • 依赖于子类实现特定的 tryAcquiretryRelease 等方法来定义同步语义。
  • 使用 CAS 操作保证并发安全。
  • 提供了模板方法简化同步器的实现。
  • 支持条件变量 (ConditionObject) 实现更复杂的等待/通知机制。

理解 AQS 的原理是深入学习 java.util.concurrent 包中各种锁和同步工具的关键。例如,ReentrantLockCountDownLatchSemaphore 等都是基于 AQS 实现的,它们的行为和特性都与 AQS 的基本机制密切相关。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值