一 前言  

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

二 什么是AQS

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

三 AQS的内部结构

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

四 内部结构源码解析

3.1 类的继承关系

  

  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关键字作用的请前去阅读下。

属性名称 描述
int waitStatus 表示节点的状态。其中包含的状态有:

CANCELLED,值为1,表示当前的线程被取消;节点进入该状态就不会变化。

SIGNAL,值为-1,表示当前节点的后继节点包含的线程需要运行,也就是unpark;

变化情况:当当前节点的线程如果释放了同步状态或者被取消,将会通知后继节点,

使后继节点的线程得以运行。

CONDITION,值为-2,表示当前节点在等待condition,也就是在condition队列中;

变化情况:当其他线程对Condition调用了signal()方法后,该节点将会从等待队列中转移到同步队列中,

加入到同步状态的获取中。

PROPAGATE,值为-3,表示当前场景下后续的acquireShared能够得以执行;

初始状态:值为0,表示当前节点在sync队列中,等待着获取锁。

Node prev
前驱节点,比如当前节点被取消,那就需要前驱节点和后继节点来完成连接。
Node next
后继节点。
Thread thread
入队列时的当前线程。
 Node nextWaiter
存储condition队列中的后继节点。

  接下来简单看看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,多个线程对其的获取将会产生一个链式的结构。

Java并发编程(2) AbstractQueuedSynchronizer的内部结构的更多相关文章

  1. Java并发编程系列-AbstractQueuedSynchronizer

    原创作品,可以转载,但是请标注出处地址:https://www.cnblogs.com/V1haoge/p/10566625.html 一.概述 AbstractQueuedSynchronizer简 ...

  2. Java并发编程(2) AbstractQueuedSynchronizer的设计与实现

    一 前言 上一篇分析AQS的内部结构,其中有介绍AQS是什么,以及它的内部结构的组成,那么今天就来分析下前面说的内部结构在AQS中的具体作用(主要在具体实现中体现). 二 AQS的接口和简单示例 上篇 ...

  3. 学习笔记:java并发编程学习之初识Concurrent

    一.初识Concurrent 第一次看见concurrent的使用是在同事写的一个抽取系统代码里,当时这部分代码没有完成,有许多的问题,另一个同事接手了这部分代码的功能开发,由于他没有多线程开发的经验 ...

  4. Java并发编程锁系列之ReentrantLock对象总结

    Java并发编程锁系列之ReentrantLock对象总结 在Java并发编程中,根据不同维度来区分锁的话,锁可以分为十五种.ReentranckLock就是其中的多个分类. 本文主要内容:重入锁理解 ...

  5. 【Java并发编程实战】-----“J.U.C”:CountDownlatch

    上篇博文([Java并发编程实战]-----"J.U.C":CyclicBarrier)LZ介绍了CyclicBarrier.CyclicBarrier所描述的是"允许一 ...

  6. JAVA并发编程J.U.C学习总结

    前言 学习了一段时间J.U.C,打算做个小结,个人感觉总结还是非常重要,要不然总感觉知识点零零散散的. 有错误也欢迎指正,大家共同进步: 另外,转载请注明链接,写篇文章不容易啊,http://www. ...

  7. 并发编程 20—— AbstractQueuedSynchronizer 深入分析

    Java并发编程实践 目录 并发编程 01—— ThreadLocal 并发编程 02—— ConcurrentHashMap 并发编程 03—— 阻塞队列和生产者-消费者模式 并发编程 04—— 闭 ...

  8. Java并发编程:并发容器之ConcurrentHashMap(转载)

    Java并发编程:并发容器之ConcurrentHashMap(转载) 下面这部分内容转载自: http://www.haogongju.net/art/2350374 JDK5中添加了新的concu ...

  9. Java并发编程:并发容器之ConcurrentHashMap

    转载: Java并发编程:并发容器之ConcurrentHashMap JDK5中添加了新的concurrent包,相对同步容器而言,并发容器通过一些机制改进了并发性能.因为同步容器将所有对容器状态的 ...

随机推荐

  1. Python 的 “Magic” 方法

    在以前的文章中,我聊过了Python的 __getitem__ 和 __setitem__ 方法.这些方法被称为“魔法”方法.特殊方法或者dunger方法(译者:国内书籍用“魔法”一词较多).那么,什 ...

  2. jenkins 通过maven部署Tomcat8报错

    问题过程 jenkins执行构建过程中,可以在workspace/项目名/target/目录下产生xxx.jar文件 但是在执行构建后操作时报出如下错误 [INFO] ---------------- ...

  3. Theme Section HDU - 4763(些许暴力)

    题意: 求出最长公共前后缀 不能重叠  而且 这个前后缀 在串的中间也要出现一次 解析: 再明确一次next数组的意思:完全匹配的最长前后缀长度 求一遍next 然后暴力枚举就好了 #include ...

  4. BZOJ 2878 【NOI2012】 迷失游乐园

    题目链接:迷失游乐园 这道题也没有传说中的那么难写吗→_→ 似乎有篇博客讲得特详细……附上链接:戳这里 如果这道题不是基环树,而就是一棵树的话,我们来考虑改怎么做.因为树上的路径只有向上.向下两种走法 ...

  5. Cobalt Strike 3.13的新功能

    Cobalt Strike 3.13现已推出.此版本添加了TCP Beacong,进程参数欺骗,并将Obfuscate和Sleep功能扩展到SMB和TCP Beacons. TCP Beacon Co ...

  6. bzoj2757【scoi2012】Blinker的仰慕者

    题目描述 Blinker 有非常多的仰慕者,他给每个仰慕者一个正整数编号.而且这些编号还隐藏着特殊的意义,即编号的各位数字之积表示这名仰慕者对Blinker的重要度. 现在Blinker想知道编号介于 ...

  7. 6: Junit1_@Test

    @Test注解是测试的基础,它提供了其他作用 1.指定将会抛出的异常类型,如果没有抛出异常或者抛出的一场不属于我们指定的类型,就会算是测试失败了. @Test(expected = RuntimeEx ...

  8. Dubbo、Zookeeper集群搭建及Rose使用心得(二)

    上篇讲了一下配置,这次主要写一下这个框架开发的大概流程.这里以实现 登陆 功能为例. 一.准备工作 1.访问拦截器 用户在进行网站访问的时候,有可能访问到不存在的网页,所以,我们需要把这些链接重新定向 ...

  9. R语言外部数据读取

    0  引言 使用R语言.Python等进行数据处理的第一步就是要导入数据(也可以使用UCI数据集),下文主要根据R语言的帮助文档来介绍外部文件数据的导入方法和注意事项.下面先附上一些指令. 1 格式r ...

  10. Docker网络 Weave

    当容器分布在多个不同的主机上时,这些容器之间的相互通信变得复杂起来.容器在不同主机之间都使用的是自己的私有IP地址,不同主机的容器之间进行通讯需要将主机的端口映射到容器的端口上,而且IP地址需要使用主 ...