文章目录
AbstractQueuedSynchronizer简介
AbstractQueuedSynchronizer
抽象队列同步器,简称为AQS
,可用于构建阻塞锁或者其他相关同步器的基础框,是Java并发包的基础工具类。通过AQS
这个框架可以对同步状态原子性管理、线程的阻塞和解除阻塞、队列的管理进行统一管理。
AQS
是抽象类,并不能直接实例化,当需要使用AQS
的时候需要继承AQS
抽象类并且重写指定的方法,这些重写方法包括线程获取资源和释放资源的方式(如ReentractLock通过分别重写线程获取和释放资源的方式实现了公平锁和非公平锁),同时子类还需要负责共享变量state的维护,如当state为0时表示该锁没有被占,大于0时候代表该锁被一个或多个线程占领(重入锁),而队列的维护(获取资源失败入队、线程唤醒、线程的状态等)不需要我们考虑,AQS
已经帮我们实现好了。AQS
的这种设计模式采用的正是模板方法模式。
总结起来子类的任务有:
- 通过
CAS
操作维护共享变量state
。 - 重写资源的获取方式。
- 重写资源释放的方式。
如果对CAS和Java内存模型还不清楚的,建议先了解这两者之后再食用本文,效果更佳!CAS原理分析及ABA问题详解 什么是Java内存模型?
完成以上三个任务即可实现自己的锁。
AQS
作为J.U.C
的工具类,面向的是需要实现锁的实现者,而锁面向的是锁的使用者,这两者的区别还是需要搞清楚的。
AQS数据结构
先看AQS
有哪些重要的成员变量。
// 头结点,你直接把它当做 当前持有锁的线程 可能是最好理解的
private transient volatile Node head;
// 阻塞的尾节点,每个新的节点进来,都插入到最后,也就形成了一个链表
private transient volatile Node tail;
// 这个是最重要的,不过也是最简单的,代表当前锁的状态,0代表没有被占用,大于0代表有线程持有当前锁
// 之所以说大于0,而不是等于1,是因为锁可以重入嘛,每次重入都加上1
private volatile int state;
// 代表当前持有独占锁的线程,举个最重要的使用例子,因为锁可以重入
// reentrantLock.lock()可以嵌套调用多次,所以每次用这个来判断当前线程是否已经拥有了锁
// if (currentThread == getExclusiveOwnerThread()) {state++}
private transient Thread exclusiveOwnerThread; //继承自AbstractOwnableSynchronizer
然后再看看AQS
的内部结构,AQS
内部数据结构为一个双向链表和一个单向链表,双链表为同步队列,队列中的每个节点对应一个Node
内部类,AQS
通过控制链表的节点而达到阻塞、同步的目的,单链表为条件队列,可以把同步队列和条件队列理解成储存等待状态的线程的队列,但是条件队列中的线程并不能直接去获取资源,而要先从条件队列转到同步队列中排队获取,同步队列的唤醒结果是线程去尝试获取锁,而条件队列的唤醒结果是把线程从条件队列移到同步队列中,一个线程要么是在同步队列中,要么是在条件队列中,不可能同时存在这两个队列里面。
Java阻塞状态和等待状态的线程从Linux内核来看,都是阻塞(等待)状态,它们都会让出CPU时间片。Java为了方便管理线程将“阻塞(等待)”状态细分成了阻塞状态和等待状态,这两个状态的区别在于由谁去唤醒,是操作系统还是其他线程。Java线程请求某一个资源失败的时候就会进入阻塞状态,处于阻塞态的线程会不断请求资源,一旦请求成功,就会进入就绪队列,等待执行。而当线程调用
wait
、join
、pack
函数时候会进入等待状态,需要其它线程显性的唤醒否则会无限期的处于等待状态。
Java线程6状态图:
内部类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;
//代表节点(线程)在 Condition queue中,等待某一条件
static final int CONDITION = -2;
//代表当前节点的后继节点(线程)会传传播唤醒的操作,仅在共享模式下才有作用
static final int PROPAGATE = -3;
//代表当前节点的状态,它的取值除了以上说的CANCELLED、SIGNAL、CONDITION、PROPAGATE,同时
//还可能为0,为0的时候代表当前节点在sync队列中,阻塞着排队获取锁。
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;
}
}
每个线程都关联一个节点,节点的状态也代表着线程的状态,AQS
通过对同步队列的管理而达到对线程的管理。