juc-aqs

2023/9/20

# 1 AQS AbstractQueuedSynchronizer 队列同步器

AQS(AbstractQueuedSynchronizer)是Java中的一个重要的同步框架,它提供了一种基础框架来实现各种类型的同步器,如锁、信号量、倒计时门闩等。AQS是Java并发包(java.util.concurrent)的核心组件之一,它用于支持构建自定义的同步工具和数据结构。

AQS的核心思想是使用一个FIFO(先进先出)的等待队列来管理线程的竞争和等待。

AQS内部维护了一个状态变量,线程在尝试获取锁或执行某个操作时,首先会尝试修改这个状态,如果修改成功(比如从0修改为1),则表示线程获得了锁或操作权限;如果修改失败,则线程会被加入到等待队列中,等待状态变为满足某个条件时再次尝试获取锁或操作权限。

AQS的两个主要子类是ReentrantLock(可重入锁)和CountDownLatch(倒计时门闩)。ReentrantLock是一个可重入的互斥锁,而CountDownLatch是一个同步工具,它允许一个或多个线程等待其他线程完成一组操作。

自定义同步器时,通常需要继承AbstractQueuedSynchronizer并实现以下两个主要方法:

  1. tryAcquire:尝试获取锁或执行某个操作的逻辑。如果返回true,表示操作成功;如果返回false,表示操作失败,线程会被加入到等待队列中。
  2. tryRelease:尝试释放锁或完成某个操作的逻辑。这个方法通常用于释放资源并唤醒等待队列中的线程。

AQS的设计使得实现各种自定义的同步器变得相对容易,开发者可以根据具体的需求来编写自己的tryAcquiretryRelease方法,从而构建出高性能的同步工具。

总之,AQS(AbstractQueuedSynchronizer)是Java并发包中的一个核心组件,用于支持构建自定义的同步工具和数据结构,它通过等待队列和状态变量的方式实现了多线程的协作和同步。

# 2 优势

AQS 解决了在实现同步器时涉及当的大量细节问题,例如获取同步状态、FIFO 同步队列。基于 AQS 来构建同步器可以带来很多好处。它不仅能够极大地减少实现工作,而且也不必处理在多个位置上发生的竞争问题。

在基于 AQS 构建的同步器中,只能在一个时刻发生阻塞,从而降低上下文切换的开销,提高了吞吐量。同时在设计 AQS 时充分考虑了可伸缩性,因此 J.U.C 中,所有基于 AQS 构建的同步器均可以获得这个优势。

# 3 同步状态

AQS 的主要使用方式是继承,子类通过继承同步器,并实现它的抽象方法来管理同步状态。

AQS 使用一个 int 类型的成员变量 state表示同步状态

  • state > 0 时,表示已经获取了锁。
  • state = 0 时,表示释放了锁。

它提供了三个方法,来对同步状态 state 进行操作,并且 AQS 可以确保对 state 的操作是安全的:

  • #getState()
  • #setState(int newState)
  • #compareAndSetState(int expect, int update)

# 4 同步队列

AQS 通过内置的 FIFO 同步队列来完成资源获取线程的排队工作

  • 如果当前线程获取同步状态失败(锁)时,AQS 则会将当前线程以及等待状态等信息构造成一个节点(Node)并将其加入同步队列,同时会阻塞当前线程
  • 当同步状态释放时,则会把节点中的线程唤醒,使其再次尝试获取同步状态。

# 5. 主要内置方法

AQS 主要提供了如下方法

  • #getState():返回同步状态的当前值。
  • #setState(int newState):设置当前同步状态。
  • #compareAndSetState(int expect, int update):使用 CAS 设置当前状态,该方法能够保证状态设置的原子性。
  • 【可重写】#tryAcquire(int arg):独占式获取同步状态,获取同步状态成功后,其他线程需要等待该线程释放同步状态才能获取同步状态。
  • 【可重写】#tryRelease(int arg):独占式释放同步状态。
  • 【可重写】#tryAcquireShared(int arg):共享式获取同步状态,返回值大于等于 0 ,则表示获取成功;否则,获取失败。
  • 【可重写】#tryReleaseShared(int arg):共享式释放同步状态。
  • 【可重写】#isHeldExclusively():当前同步器是否在独占式模式下被线程占用,一般该方法表示是否被当前线程所独占。
  • acquire(int arg):独占式获取同步状态。如果当前线程获取同步状态成功,则由该方法返回;否则,将会进入同步队列等待。该方法将会调用可重写#tryAcquire(int arg) 方法;
  • #acquireInterruptibly(int arg):与 #acquire(int arg) 相同,但是该方法响应中断。当前线程为获取到同步状态而进入到同步队列中,如果当前线程被中断,则该方法会抛出InterruptedException 异常并返回。
  • #tryAcquireNanos(int arg, long nanos):超时获取同步状态。如果当前线程在 nanos 时间内没有获取到同步状态,那么将会返回 false ,已经获取则返回 true 。
  • #acquireShared(int arg):共享式获取同步状态,如果当前线程未获取到同步状态,将会进入同步队列等待,与独占式的主要区别是在同一时刻可以有多个线程获取到同步状态;
  • #acquireSharedInterruptibly(int arg):共享式获取同步状态,响应中断。
  • #tryAcquireSharedNanos(int arg, long nanosTimeout):共享式获取同步状态,增加超时限制。
  • #release(int arg):独占式释放同步状态,该方法会在释放同步状态之后,将同步队列中第一个节点包含的线程唤醒。
  • #releaseShared(int arg):共享式释放同步状态。

从上面的方法看下来,基本上可以分成 3 类:

  • 独占式获取与释放同步状态
  • 共享式获取与释放同步状态
  • 查询同步队列中的等待线程情况