虽然已经有不少前辈已经分析过AbstractQueuedSynchronizer(简称AQS,也叫队列同步器)类,可是感受那些点始终是别人的,看一遍甚至几遍终不会印象深入。因此仍是记录下来印象更深入,还能和你们一块儿探讨(这就是重复造轮子的好处,另外也主要是这篇篇幅太长了,犹豫了很久才决定写做)。既然有不少前辈都分析过这个类说明它是多么的重要,下面咱们看下concurrent包的实现示意图就清楚AQS的所占有的地位了。html
AbstractQueuedSynchronizer,中文简称队列同步器,英文简称AQS。它是用来构建锁或者其余同步组件的基础框架,它使用了一个int成员变量表示同步状态,经过内置的FIFO队列来完成资源获取线程的排队工做。从上面图能够看出AQS是实现锁或任意同步组件的关键,经过继承同步器并实现它的抽象方法来管理同步状态等。java
我的习惯喜欢先看其内部结构,由于内部结果是一个类实现的核心。通过分析得知:AQS类底层的数据结构是使用双向链表,包括head结点和tail结点,head结点主要用做后续的调度。另外还包含一个单向链表,只有当使用Condition时,才会存在此单向链表。而且可能会有多个Condition 链表(其中链表是队列的一种具体表现,因此也可称做队列)。以下图:node
一、说明它是一个抽象类,就说明它可能存在抽象方法须要子类去重写实现(具体有哪些方法须要重写后续会说明)。数据结构
二、它还继承了AbstractOwnableSynchronizer(简称AOS)类能够设置独占资源线程和获取独占资源线程(独占锁会涉及到,AOS的源码本身能够进去看看)。框架
另外建议各位多看看类上的注释,其实还蛮有做用的。函数
先分析内部类中的结构再看AQS是怎么引用它的。下面先看Node.class,主要分析都在注释上了。ui
/** * 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节点,放入队列中。每一个节点都包含了当前节点对应的线程、状态、前置节点引用、后继节点引用以及下一个等待者。spa
其中还须要注意的是waitStatus对应的各个状态表明着什么意思,另外不清楚volatile关键字做用的请前去阅读下。线程
属性名称 | 描述 |
int waitStatus | 表示节点的状态。其中包含的状态有: CANCELLED,值为1,表示当前的线程被取消;节点进入该状态就不会变化。code 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中的几个方法相似呢。
// 头结点 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,多个线程对其的获取将会产生一个链式的结构。