2024-03-23  阅读(1)
版权声明:本文为博主原创文章,遵循 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 实现。


Java 面试宝典是大明哥全力打造的 Java 精品面试题,它是一份靠谱、强大、详细、经典的 Java 后端面试宝典。它不仅仅只是一道道面试题,而是一套完整的 Java 知识体系,一套你 Java 知识点的扫盲贴。

它的内容包括:

  • 大厂真题:Java 面试宝典里面的题目都是最近几年的高频的大厂面试真题。
  • 原创内容:Java 面试宝典内容全部都是大明哥原创,内容全面且通俗易懂,回答部分可以直接作为面试回答内容。
  • 持续更新:一次购买,永久有效。大明哥会持续更新 3+ 年,累计更新 1000+,宝典会不断迭代更新,保证最新、最全面。
  • 覆盖全面:本宝典累计更新 1000+,从 Java 入门到 Java 架构的高频面试题,实现 360° 全覆盖。
  • 不止面试:内容包含面试题解析、内容详解、知识扩展,它不仅仅只是一份面试题,更是一套完整的 Java 知识体系。
  • 宝典详情:https://www.yuque.com/chenssy/sike-java/xvlo920axlp7sf4k
  • 宝典总览:https://www.yuque.com/chenssy/sike-java/yogsehzntzgp4ly1
  • 宝典进展:https://www.yuque.com/chenssy/sike-java/en9ned7loo47z5aw

目前 Java 面试宝典累计更新 400+ 道,总字数 42w+。大明哥还在持续更新中,下图是大明哥在 2024-12 月份的更新情况:

想了解详情的小伙伴,扫描下面二维码加大明哥微信【daming091】咨询

同时,大明哥也整理一套目前市面最常见的热点面试题。微信搜[大明哥聊 Java]或扫描下方二维码关注大明哥的原创公众号[大明哥聊 Java] ,回复【面试题】 即可免费领取。

阅读全文