文章目录
AQS简介
- AQS是 AbstractQueueSynchronizer 虚拟队列同步器,是一个虚拟类,子类只需实现其5个方法中的三个就可以构造一个可获取独占锁或获取共享锁的类,没有获取到锁的线程会进入队列排队
- 这个类负责实现排队操作,队列中每个节点内部都有一个阻塞的线程,队头的节点是获取锁的线程,队头节点负责唤醒后续一个节点
- 其父类 AbstractOwnableSynchronizer 负责记录和设置当前获取锁的线程
AbstractOwnableSynchronizer 源码可以看我这篇文章 AbstractOwnableSynchronizer - 光看AQS操作可能有些抽象,也不知道它在干嘛,可以看看其子类的实现如 ReentrantLock
ReentrantLock 源码可以看我这篇文章 ReentrantLock
AQS子类需要实现的方法
方法名 | 作用 |
---|---|
boolean tryAcquire(int arg) | 尝试获取独占锁,没有成功则排队 |
boolean tryRelease(int arg) | 尝试释放独占锁 |
iint tryAcquireShared(int arg) | 尝试获取共享锁,没有成功则排队 |
boolean tryReleaseShared(int arg) | 尝试释放共享锁 |
boolean isHeldExclusively() | 判断当前线程是否独占锁 |
AQS构成
AQS队列节点
- SHARED 将该队列标记为共享模式即多个线程可以获取到锁,EXCLUSIVE 将该队列标记为独占模式即只有单个线程可以获取到锁
- CANCELLED、SIGNAL、CONDITION、PROPAGATE都是节点的状态值,它们会把值赋给waitStatus
- prev、next说明AQS是由双向链表实现的队列
- thread说明每个Node节点对应一个线程,说明有一个线程在里面阻塞并排队
- nextWaiter是下一个条件队列节点
- NEXT、PREV、THREAD、WAITSTATUS是变量句柄,是JDK9新特性,通过它们可以对应的变量进行CAS操作和原子操作,不需要再像以前把变量定义为原子类变量了,如NEXT可以对next进行CAS和原子性操作,如果想要达到这种效果只有把prev定义为AtomicReference< 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;
static final int CONDITION = -2;
static final int PROPAGATE = -3;
volatile int waitStatus;
volatile Node prev;
volatile Node next;
volatile Thread thread;
Node nextWaiter;
private static final VarHandle NEXT;
private static final VarHandle PREV;
private static final VarHandle THREAD;
private static final VarHandle WAITSTATUS;
}
AQS队列
- head就是队列的头节点,tail就是队列的尾节点,state在其子类实现一般理解为获取锁的次数
- STATE、HEAD、TAIL 是对应变量的变量句柄
public abstract class AbstractQueuedSynchronizer
extends AbstractOwnableSynchronizer
implements java.io.Serializable {
private transient volatile Node head;
private transient volatile Node tail;
private volatile int state;
private static final VarHandle STATE;
private static final VarHandle HEAD;
private static final VarHandle TAIL;
}
条件队列
可以看出条件队列里面也是使用Node节点作为其组成,只是使用的是Node节点的nextWaiter属性,相当于单向链表
public class ConditionObject implements Condition, java.io.Serializable {
private transient Node firstWaiter;
private transient Node lastWaiter;
}
操作
查询操作
get操作(获取)
getExclusiveQueuedThreads操作
作用:获取队列中处于独占锁模式的线程列表
实现:从双向链表尾部开始查找,尾部查找原因在插入enq里面,如果Node节点不是共享节点,则取出节点中存放的线程并将其加入列表
public final Collection<Thread> getExclusiveQueuedThreads() {
ArrayList<Thread> list = new ArrayList<>();
for (Node p = tail; p != null; p = p.prev) {
if (!p.isShared()) {
Thread t = p.thread;
if (t != null)
list.add(t);
}
}
return list;
}
getFirstQueuedThread操作
作用:获取队列中第一个等待的线程
实现:如果头节点等于尾结点说明没有线程排队,否则执行fullGetFirstQueuedThread返回队头后面一个结点内部的线程,或者后续结点内部的线程
public final Thread getFirstQueuedThread() {
// handle only fast path, else relay
// 仅处理快速路径,否则为转发
return (head == tail) ? null : fullGetFirstQueuedThread();
}
fullGetFirstQueuedThread操作
- 连续检查两次,队头h不为空 ,且队头下一个节点s也不为空,且s的前一个节点为队头h,且s存储的线程也不为空,则返回队头下一个节点的线程st
- 为什么不返回队头节点内部的线程,因为队头节点内部的线程一般为null,为null表示已经获取到锁在运行了,不为null说明仍然在排队
- 如果以上条件不满足,则从队列尾部开始往前找,找到最靠近队头的一个内部线程不为空的节点,再返回该节点内部的线程
private Thread fullGetFirstQueuedThread() {
Node h, s;
Thread st;
if ( //队头不为空 且 队头下一个也不为空 且队头的下一个的前一个为队头 且存储的线程也不为空
((h = head) != null && (s = h.next) != null &&
s.prev == head && (st = s.thread) != null)
||
//队头不为空 且 队头下一个也不为空 且队头的下一个的前一个为队头 且存储的线程也不为空
((h = head) != null && (s = h.next) != null &&
s.prev == head && (st = s.thread) != null)
)
return st;
Thread firstThread = null;
// 则从队列尾部开始往前找,找到最靠近队头的一个内部线程不为空的节点
for (Node p = tail; p != null && p != head; p = p.prev) {
Thread t = p.thread;
if (t != null)
firstThread = t;
}
return firstThread;
}
getQueuedThreads操作
作用:获取队列中等待的线程集合
实现:从双向链表尾部开始查找,如果Node节点内部线程不为空,则取出节点中存放的线程并将其加入列表
public final Collection<Thread> getQueuedThreads() {
ArrayList<Thread> list = new ArrayList<>();
for (Node p = tail; p != null; p = p.prev) {
Thread t = p.thread;
if (t != null)
list.add(t);
}
return list;
}
getQueueLength操作
作用:获取队列中等待的线程数量
实现:从双向链表尾部开始查找,如果Node节点内部线程不为空,则计数器加1
public final int getQueueLength() {
int n = 0;
for (Node p = tail; p != null; p = p.prev) {
if (p.thread != null)
++n;
}
return n;
}
getSharedQueuedThreads操作
作用:获取队列中共享(共享锁)结点等待的线程集合
实现:从双向链表尾部开始查找,如果Node节点是共享结点且内部线程不为空,则取出节点中存放的线程并将其加入列表
public final Collection<Thread> getSharedQueuedThreads() {
ArrayList<Thread> list = new ArrayList<>();
for (Node p = tail; p != null; p = p.prev) {
if (p.isShared()) {
Thread t = p.thread;
if (t != null)
list.add(t)