2024-03-23
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。 本文链接:https://www.skjava.com/mianshi/baodian/detail/2065042012

回答

AQS 是 AbstractQueuedSynchronizer 简称,它是 J.U.C 包中多个组件的底座,如 ReentrantLockCountDownLatchSemaphoreCyclicBarrier 都是基于 AQS 来实现的。

AQS 底层利用 volatile 类型的 int state 来表示同步状态,该字段的含义取决于实现的同步器,如在ReentrantLock 中,state 表示持有锁的数量,在 Semaphore 中,state 表示可用的许可证数量。当线程来获取同步锁时,如果 status = 0,说明目前没有任何线程占有锁,该线程可以获得锁并设置 state = 1。如果 state > 0,则说明有线程正在持有锁,则线程必须加入同步队列进行等待(不考虑重入)。

AQS 内部还维护着一个 FIFO 的双向同步队列,该队列通过 Node 的实例来构建的,每个 Node 代表一个等待获取资源的线程。获取锁失败的线程都会被包装成一个 Node 节点加入队列中并进入阻塞状态。

AQS 有两种锁机制:排他锁和共享锁。

  • 排他锁:同一个时间只能有一个线程访问该共享资源,为独占。
  • 共享锁:允许多个线程同时访问共享资源,一般来说它是读锁。

详解

AQS 设计

先看 AQS 有哪些重要属性:

// 头结点
private transient volatile Node head;

// 尾节点
private transient volatile Node tail;

// AQS 中最重要的变量。它表示当前锁资源的状态
// = 0 时,表示当前锁没有被占用
// > 0 时,表示有线程持有锁
// state 可以大于 1,因为锁支持可重入
private volatile int state;

// 当前持有所有的线程
// 这个变量位于 AbstractOwnableSynchronizer 中,为了更好地理解,大明哥将其放到这里来了
private transient Thread exclusiveOwnerThread;

AQS 内部有一个 FIFO 的同步队列,即 CLH 同步队列,如果某个线程获取锁失败,则会被包装为一个 Node 节点加入到 CLH 同步队列中,同时被阻塞。其结构如下:

  • head:CLH 队列的头结点,我们可以认为它就是持有锁的当前线程的节点。而且,head 节点是延迟初始化的,只有它在首次使用时才会被创建。有两点需要注意:
    • 阻塞队列不包含 head 节点
    • 如果 head 节点存在,那么它的 waitStatus 一定不会是 CANCELLED。这说明 head 不会表示一个已取消其等待状态的线程。
  • tail:CLH 队列的尾节点,当某个线程获取锁失败时,会被加入到 CLH 队列的尾部,同时 tail 会指向这个节点。

每一个等待的线程都会被包装成一个 Node 实例:

  static final class Node {
        /** 标识节点当前处于共享模式下 */
        static final Node SHARED = new Node();
        /** 标识节点当前处于独占模式下 */
        static final Node EXCLUSIVE = null;

        /** 表示此线程因为超时或中断而不再需要争夺这个锁了 */
        static final int CANCELLED =  1;
        /** 表示当前节点的线程释放或取消时,应该通知后继节点的线程 */
        static final int SIGNAL    = -1;
        /** 表示节点在条件队列中,与条件变量相关联 */
        static final int CONDITION = -2;
        /** 表示下一个 acquireShared 应该无条件地传播  */
        static final int PROPAGATE = -3;

        /* 节点状态,有上面四个值(1、-1、-2、-3) */
        volatile int waitStatus;

        /* 前驱节点 */
        volatile Node prev;

        /* 后继节点*/
        volatile Node next;

        /* 包装的线程*/
        volatile Thread thread;

        /* 这个字段用于链接下一个等待在条件上的节点 ,与 next 意义一致,但是它用于条件队列*/
        Node nextWaiter;

    }

下图是 AQS 的模型图:

AQS 底层采用模板方法模式,子类需要实现对共享成员变量 state 的获取和释放,即获取锁和释放锁。至于 CLH 队列的维护、线程的阻塞和唤醒由 AQS 实现。

阅读全文