Java并发编程(2) AbstractQueuedSynchronizer的内部结构

简介: 既然有很多前辈都分析过这个类说明它是多么的重要,下面我们看下concurrent包的实现示意图就清楚AQS的所占有的地位了。

一 前言  

虽然已经有很多前辈已经分析过AbstractQueuedSynchronizer(简称AQS,也叫队列同步器)类,但是感觉那些点始终是别人的,看一遍甚至几遍终不会印象深刻。所以还是记录下来印象更深刻,还能和大家一起探讨(这就是重复造轮子的好处,另外也主要是这篇篇幅太长了,犹豫了好久才决定写作)。既然有很多前辈都分析过这个类说明它是多么的重要,下面我们看下concurrent包的实现示意图就清楚AQS的所占有的地位了。
_1

二 什么是AQS

AbstractQueuedSynchronizer,中文简称队列同步器,英文简称AQS。它是用来构建锁或者其他同步组件的基础框架,它使用了一个int成员变量表示同步状态,通过内置的FIFO队列来完成资源获取线程的排队工作。从上面图可以看出AQS是实现锁或任意同步组件的关键,通过继承同步器并实现它的抽象方法来管理同步状态等。

三 AQS的内部结构

个人习惯喜欢先看其内部结构,因为内部结果是一个类实现的核心。经过分析得知:AQS类底层的数据结构是使用双向链表,包括head结点和tail结点,head结点主要用作后续的调度。另外还包含一个单向链表,只有当使用Condition时,才会存在此单向链表。并且可能会有多个Condition 链表(其中链表是队列的一种具体表现,所以也可称作队列)。如下图:

_2

四 内部结构源码解析

3.1 类的继承关系
_3

1、说明它是一个抽象类,就说明它可能存在抽象方法需要子类去重写实现(具体有哪些方法需要重写后续会说明)。

  2、它还继承了AbstractOwnableSynchronizer(简称AOS)类可以设置独占资源线程和获取独占资源线程(独占锁会涉及到,AOS的源码自己可以进去看看)。

  另外建议各位多看看类上的注释,其实还蛮有作用的。

3.2 类的内部类

  先分析内部类中的结构再看AQS是怎么引用它的。下面先看Node.class,主要分析都在注释上了。

/**
 * Wait queue node class.
 * 注意看类上的注释,上面是原注释的第一行,表示等待队列节点类(虽然实际上是一个双向链表)。
 */
static final class Node {
    /**
     * 总共分为两者模式:共享和独占
     */
    /** 在共享模式中等待的节点 */
    static final Node SHARED = new Node();
    /** 在独占模式中等待的节点 */
    static final Node EXCLUSIVE = null;

    /**
     * 下面几个表示节点状态,也就是waitStatus所具有可能的值。
     */
    /**
     * 标记线程处于取消状态
     * 节点进入该状态就不会变化。
     * /
    static final int CANCELLED =  1;
    /**
     * 标记后继节点的线程处于等待状态,需要被取消停放(即被唤醒unpark)。
     * 变化情况:当当前节点的线程如果释放了同步状态或者被取消,将会通知后继节点,使后继节点的线程得以运行。
     */
    static final int SIGNAL    = -1;
    /**
     * 标记线程正在等待条件(Condition),也就是该节点处于等待队列中。
     * 变化情况:当其他线程对Condition调用了signal()方法后,该节点将会从等待队列中转移到同步队列中,加入到同步状态的获取中。
     */
    static final int CONDITION = -2;
    /**
     * 表示下一次共享式同步状态获取将会无条件的被传播下去。
     */
    static final int PROPAGATE = -3;

    /**
     * 节点状态,包含上面四种状态(另外还有一种初始化状态0)
     * 特别注意:它是volatile关键字修饰的,保证对其线程可见性,但是不保证原子性。
     * 所以更新状态时,采用CAS方式去更新, 如:compareAndSetWaitStatus
     */
    volatile int waitStatus;

    /**
     * 前驱节点,比如当前节点被取消,那就需要前驱节点和后继节点来完成连接。
     */
    volatile Node prev;

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

    /**
     * 入队列时的当前线程。
     */
    volatile Thread thread;

    /**
     * 存储condition队列中的后继节点。
     */
    Node nextWaiter;

    /**
     * 判断是否共享模式
     */
    final boolean isShared() {
        return nextWaiter == SHARED;
    }

    /**
     * 获取前置节点,如果前置节点为空就抛出异常
     */
    final Node predecessor() throws NullPointerException {
        Node p = prev;
        if (p == null)
            throw new NullPointerException();
        else
            return p;
    }
    // 省略三个构造函数
}

总结下:当每个线程被阻塞时都会封装成一个Node节点,放入队列中。每个节点都包含了当前节点对应的线程、状态、前置节点引用、后继节点引用以及下一个等待者。

  其中还需要注意的是waitStatus对应的各个状态代表着什么意思,另外不清楚volatile关键字作用的请前去阅读下。

_4

接下来简单看看ConditionObject的源码,后续我们会单独分析下这个类的作用。

/**
 * 实现Condition接口
 */
public class ConditionObject implements Condition, java.io.Serializable {
    private static final long serialVersionUID = 1173984872572414699L;
    /**
     * 条件队列的第一个节点。
     */
    private transient AbstractQueuedSynchronizer.Node firstWaiter;
    /**
     * 条件队列的最后一个节点。
     */
    private transient AbstractQueuedSynchronizer.Node lastWaiter;
}

从中可以看它还是实现了Condition接口,而Condition接口又定义了什么规范呢?自己去看:),你会不会发现有点跟Object中的几个方法类似呢。

3.3 主要内部成员

    // 头结点
    private transient volatile Node head;
    // 尾结点
    private transient volatile Node tail;
    // 同步状态
    private volatile int state;

五 总结

通过上述分析就很清楚其内部结构是什么了吧。总结下:

  节点(Node)是成为sync队列和condition队列构建的基础,在同步器中就包含了sync队列(Node双向链表)。同步器拥有三个成员变量:sync队列的头结点head、sync队列的尾节点tail和状态state。对于锁的获取,请求形成节点,将其挂载在尾部,而锁资源的转移(释放再获取)是从头部开始向后进行。对于同步器维护的状态state,多个线程对其的获取将会产生一个链式的结构。

文章来源:https://www.cnblogs.com/yuanfy008/p/9608666.html

推荐阅读:https://www.roncoo.com/course/list.html?courseName=%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B

相关文章
|
4月前
|
IDE Java 编译器
java编程最基础学习
Java入门需掌握:环境搭建、基础语法、面向对象、数组集合与异常处理。通过实践编写简单程序,逐步深入学习,打牢编程基础。
275 1
|
4月前
|
Java
如何在Java中进行多线程编程
Java多线程编程常用方式包括:继承Thread类、实现Runnable接口、Callable接口(可返回结果)及使用线程池。推荐线程池以提升性能,避免频繁创建线程。结合同步与通信机制,可有效管理并发任务。
214 6
|
4月前
|
安全 前端开发 Java
从反射到方法句柄:深入探索Java动态编程的终极解决方案
从反射到方法句柄,Java 动态编程不断演进。方法句柄以强类型、低开销、易优化的特性,解决反射性能差、类型弱、安全性低等问题,结合 `invokedynamic` 成为支撑 Lambda 与动态语言的终极方案。
206 0
|
5月前
|
SQL Java 数据库
2025 年 Java 从零基础小白到编程高手的详细学习路线攻略
2025年Java学习路线涵盖基础语法、面向对象、数据库、JavaWeb、Spring全家桶、分布式、云原生与高并发技术,结合实战项目与源码分析,助力零基础学员系统掌握Java开发技能,从入门到精通,全面提升竞争力,顺利进阶编程高手。
980 1
|
5月前
|
Java 开发者
Java并发编程:CountDownLatch实战解析
Java并发编程:CountDownLatch实战解析
506 100
|
5月前
|
NoSQL Java 关系型数据库
超全 Java 学习路线,帮你系统掌握编程的超详细 Java 学习路线
本文为超全Java学习路线,涵盖基础语法、面向对象编程、数据结构与算法、多线程、JVM原理、主流框架(如Spring Boot)、数据库(MySQL、Redis)及项目实战等内容,助力从零基础到企业级开发高手的进阶之路。
417 1
|
5月前
|
算法 Java
Java多线程编程:实现线程间数据共享机制
以上就是Java中几种主要处理多线程序列化资源以及协调各自独立运行但需相互配合以完成任务threads 的技术手段与策略。正确应用上述技术将大大增强你程序稳定性与效率同时也降低bug出现率因此深刻理解每项技术背后理论至关重要.
407 16
|
6月前
|
安全 Java Shell
Java模块化编程(JPMS)简介与实践
本文全面解析Java 9模块化系统(JPMS),帮助开发者解决JAR地狱、类路径冲突等常见问题,提升代码的封装性、性能与可维护性。内容涵盖模块化核心概念、module-info语法、模块声明、实战迁移、多模块项目构建、高级特性及最佳实践,同时提供常见问题和面试高频题解析,助你掌握Java模块化编程精髓,打造更健壮的应用。
|
6月前
|
安全 算法 Java
Java泛型编程:类型安全与擦除机制
Java泛型详解:从基础语法到类型擦除机制,深入解析通配符与PECS原则,探讨运行时类型获取技巧及最佳实践,助你掌握泛型精髓,写出更安全、灵活的代码。
|
6月前
|
安全 Java 数据库连接
2025 年最新 Java 学习路线图含实操指南助你高效入门 Java 编程掌握核心技能
2025年最新Java学习路线图,涵盖基础环境搭建、核心特性(如密封类、虚拟线程)、模块化开发、响应式编程、主流框架(Spring Boot 3、Spring Security 6)、数据库操作(JPA + Hibernate 6)及微服务实战,助你掌握企业级开发技能。
857 3