一 前言
虽然已经有很多前辈已经分析过 AbstractQueuedSynchronizer(简称 AQS)类, 但是感觉那些点始终是别人的, 看一遍甚至几遍终不会印象深刻. 所以还是记录下来印象更深刻, 还能和大家一起探讨(这就是重复造轮子的好处, 另外也主要是这篇篇幅太长了, 犹豫了好久才决定写作). 既然有很多前辈都分析过这个类说明它是多么的重要, 下面我们看下 concurrent 包的实现示意图就清楚 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 | 表示节点的状态. 其中包含的状态有:
|
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, 多个线程对其的获取将会产生一个链式的结构.
来源: https://www.cnblogs.com/yuanfy008/p/9608666.html