AbstractQueuedSynchronizer(AQS)源码深度解析

AbstractQueuedSynchronizer(简称 AQS)是 Java 并发包(java.util.concurrent)中的核心类之一,主要用于实现自定义同步器,如互斥锁、信号量、读写锁等。AQS 是一个抽象类,提供了多种方法来处理线程的同步操作,它的实现基于一个先进先出的(FIFO)的等待队列。

AQS 采用了一个 同步状态 来管理线程的访问权限,同时使用 等待队列 来协调多线程的竞争。通过对 AQS 源码的解析,我们可以更好地理解它如何在 Java 中提供高效的并发控制。

AQS 核心设计

  1. 同步状态 AQS 的同步状态通常是一个整数,表示同步资源的状态。它通过 getState()setState()compareAndSetState() 等方法来获取和修改这个状态。
  2. 等待队列 AQS 使用一个 FIFO 队列 来管理那些被阻塞的线程。每个线程在等待时都会被封装成一个 节点,这些节点会按顺序排列在队列中。AQS 通过这个队列来控制线程的排队顺序。
  3. 独占与共享模式 AQS 支持两种模式:独占模式共享模式。独占模式下只有一个线程能够获取同步状态;共享模式下多个线程可以共享同步资源。

AQS 重要方法解析

  1. acquire()release()
    • acquire():用于获取同步状态。线程会被阻塞,直到同步状态可用为止。
    • release():释放同步状态,使得其他等待的线程可以获取到该状态。
  2. tryAcquire()tryRelease() 这些方法是实现自定义同步器的关键方法,它们需要被子类实现。tryAcquire() 用于尝试获取同步状态,tryRelease() 用于尝试释放同步状态。
  3. acquireShared()releaseShared() 这两个方法用于共享模式下的同步控制。当资源可以被多个线程共享时,acquireShared() 会尝试获得共享状态,releaseShared() 会释放共享状态。
  4. isHeldExclusively() 该方法用于判断当前同步器是否被某个线程独占。它是判断同步器状态是否为独占状态的标准。

AQS 的工作原理

  1. 线程获取锁 线程通过 acquire() 方法尝试获取同步状态。若同步状态可用,则线程直接获取;若不可用,线程会被挂起并放入等待队列中。
  2. 线程释放锁 当线程完成任务并调用 release() 方法时,它会将同步状态释放,唤醒队列中的一个或多个等待线程。
  3. 等待队列 当线程被阻塞时,它会被加入到 AQS 的等待队列中。等待队列中的线程是先进先出(FIFO)排队的,保证了公平性。
  4. 共享模式 在共享模式下,多个线程可以同时获取同步状态。例如,信号量和读写锁的实现就是基于共享模式的。

AQS 源码解析

AQS 类的主要成员变量

// AQS 内部的一个节点类,用于表示等待队列中的每个节点。
private static final class Node {
    static final Node EXCLUSIVE = null; // 独占模式
    static final Node SHARED = new Node(); // 共享模式
    
    volatile Thread thread; // 该节点所代表的线程
    volatile Node next; // 下一个节点
    volatile Node prev; // 上一个节点
    volatile int waitStatus; // 节点的等待状态
    volatile Node nextWaiter; // 下一个等待者节点
}

等待队列的管理

AQS 内部维护着一个等待队列,通过 enqueue(Node node) 方法将线程节点加入队列:

// 将节点加入队列
private Node enqueue(Node node) {
    // 获取队列的最后一个节点
    Node pred = tail;
    if (pred != null) {
        node.prev = pred;
        if (compareAndSetTail(pred, node)) {
            pred.next = node;
            return pred;
        }
    }
    return head;
}

获取同步状态

// 通过 CAS 操作获取同步状态
protected final boolean compareAndSetState(int expect, int update) {
    return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
}

AQS 的使用场景

  1. 锁的实现 AQS 是实现 Java 中 ReentrantLockReentrantReadWriteLock 等自定义锁的基础。它通过 独占模式 来实现一个线程获取锁,而其他线程等待。
  2. 信号量 在信号量的实现中,AQS 使用 共享模式 来允许多个线程并发访问资源。例如,Semaphore 类就是基于 AQS 实现的,它允许多个线程共享访问。
  3. 计数器 AQS 可以用于实现各种计数器,例如 CountDownLatchCyclicBarrier,这些计数器的实现都基于 AQS 的同步状态管理和线程的等待机制。

AQS 的优缺点

优点

  • 高效性:AQS 通过自旋和 CAS 操作来减少上下文切换,提高性能。
  • 灵活性:AQS 支持多种同步模式,能够实现各种同步器(如独占、共享模式的锁和信号量)。
  • 可扩展性:用户可以通过继承 AQS 类来实现自定义的同步器。

缺点

  • 复杂性:AQS 的实现比较复杂,理解和调试困难,尤其是等待队列和同步状态的管理。
  • 公平性问题:尽管 AQS 支持公平性控制,但默认是非公平的,可能会导致线程饥饿等问题。

总结

AbstractQueuedSynchronizer(AQS)是 Java 并发工具包的一个核心组件,提供了一个框架用于实现锁、信号量、计数器等多种同步机制。它通过同步状态和等待队列来管理线程的访问,支持独占模式和共享模式。理解 AQS 的实现原理,能够帮助开发者更好地构建高效的并发应用。

THE END