JDK11
JDK并发包中的CountDownLatch,ReentrantLock,ThreadPoolExecutor,Semaphore,ReentrantReadWriteLock 都是继承自AbstractQueuedSynchronizer
这个抽象类,其本质使用一个双向链表维护一个FIFO队列,用state来维护资源的状态以此用于实现上面那些子类的锁语义。
若被请求的共享资源空闲,则会把当前请求资源的线程设置为有效的工作线程,并且把共享资源设置为锁定状态。如果被请求的共享资源被占用,就把线程封装为Node加入到一个虚拟队列中,具体是通过CLH队列锁的方式实现。CLH本质上是用前一结点某一属性表示当前结点的状态。
那么在这些子类中资源分别表示什么意思呢?这里参考ref给出一个表格
AQS有两种队列,一种是同步队列,一种是条件队列
同步队列是一个双向链表,而条件队列只有在使用Condition的时候才会创建,是一个单向队列。至于两者怎么用,得要等到后面才介绍了。队列中的结点本质上是对线程的包装,分为独享型和共享型两种:
public abstract class AbstractQueuedSynchronizer
extends AbstractOwnableSynchronizer
implements Serializable
可以看见继承关系并不复杂,我们首先来看其抽象父类AbstractOwnableSynchronizer
的代码
提供了设置独占线程核获取独占线程的方法
public abstract class AbstractOwnableSynchronizer
implements java.io.Serializable {
/** Use serial ID even though all fields transient. */
private static final long serialVersionUID = 3737899427754241961L;
/**
* Empty constructor for use by subclasses.
*/
protected AbstractOwnableSynchronizer() { }
/**
* The current owner of exclusive mode synchronization.
*/
private transient Thread exclusiveOwnerThread;
/**
* Sets the thread that currently owns exclusive access.
* A {@code null} argument indicates that no thread owns access.
* This method does not otherwise impose any synchronization or
* {@code volatile} field accesses.
* @param thread the owner thread
*/
protected final void setExclusiveOwnerThread(Thread thread) {
exclusiveOwnerThread = thread;
}
/**
* Returns the thread last set by {@code setExclusiveOwnerThread},
* or {@code null} if never set. This method does not otherwise
* impose any synchronization or {@code volatile} field accesses.
* @return the owner thread
*/
protected final Thread getExclusiveOwnerThread() {
return exclusiveOwnerThread;
}
}
static final class Node
作为同步队列与条件队列的结点。每一个等待锁的线程会封装成一个Node放入队列,每个Node有一个对Thread的引用,所以实际上是Thread在排队,然后每个Node还有一个waitStatus,用来表示线程的状态。
/** Marker to indicate a node is waiting in shared mode */
static final Node SHARED = new Node(); // 【共享】模式
/** Marker to indicate a node is waiting in exclusive mode */
static final Node EXCLUSIVE = null; // 【独占】模式
/*
* 该标记指示结点应当被取消,不再参与排队
* 如果在线程阻塞期间发生异常的话,会为其所在的结点设置此标记
*/
static final int CANCELLED = 1;
// SIGNAL,值为-1,表示当前节点的后继节点包含的线程需要运行,也就是unpark
static final int SIGNAL = -1;
// CONDITION,值为-2,表示当前节点在条件队列中
static final int CONDITION = -2;
/* 当一个共享结点没有经过阻塞就直接获取锁时,
* 会将头结点更新为该结点,并且为该结点设置PROPAGATE标记,
* 接下来,如果其后续结点也不需要经过阻塞,那么该结点保持PROPAGATE标记,
* 反之,如果其后续结点需要经过阻塞,那么该标记会被修改为SIGNAL
* /
* //只会在队列的头节点设置
static final int PROPAGATE = -3;
状态默认为0,表示结点在同步队列中
volatile保证线程可见
volatile int waitStatus;
// node内存储的线程引用,表面上是node在排队,实际上是thread在排队
volatile Thread thread;
// 用于【|同步队列|】,表示排队结点的后继,顺着后继遍历可以找到陷入阻塞的node线程
volatile Node next;
volatile Node prev;
/*
* 用于【|同步队列|】时,该引用仅作为标记使用
* 它表示参与排队的node是[独占模式node],或者是[共享模式node]
*
* 用于【|条件队列|】时,该引用表示参与排队的下一个结点
*/
Node nextWaiter;
/** Establishes initial head or SHARED marker. */
// 创建一个空的Node,用作头结点或共享标记
Node() {
}
/** Constructor used by addWaiter. */
// 创建一个独占/共享模式的node
Node(Node nextWaiter) {
this.nextWaiter = nextWaiter; // 记录当前node的模式
THREAD.set(this, Thread.currentThread()); // 继续当前线程
}
/** Constructor used by addConditionWaiter. */
// 创建一个状态为waitStatus的node
Node(int waitStatus) {
WAITSTATUS.set(this, waitStatus);
THREAD.set(this, Thread.currentThread());
}
public class ConditionObject implements Condition, Serializable
这个类方法比较多,具体实现等会再分析,不过我们可以从继承关系推断,这个类相当于AQS内部的Condition,提供wait,signal等线程通信的操作。
// {同步条件}对象,用于更精细地指导线程的同步行为
public class ConditionObject implements Condition, Serializable {
private static final long serialVersionUID = 1173984872572414699L;
/** Mode meaning to reinterrupt on exit from wait */
// 刚刚唤醒的线程带有中断标记,且该线程node仍在【|条件队列|】,此时需要为线程恢复中断标记
private static final int REINTERRUPT = 1;
/** Mode meaning to throw InterruptedException on exit from wait */
// 刚刚唤醒的线程带有中断标记,且该线程node已进入了【|同步队列|】,此时需要抛出异常
private static final int THROW_IE = -1;
// 【|条件队列|】以单链表形式组织,firstWaiter和lastWaiter是首尾结点,不存在头结点
private transient Node firstWaiter, lastWaiter;
// 申请独占锁,允许阻塞带有中断标记的线程(会先将其标记清除)
public final void acquire(int arg) {
// 尝试申请独占锁
if(!tryAcquire(arg)){
/*
* 如果当前线程没有申请到独占锁,则需要去排队
* 注:线程被封装到Node中去排队
*/
// 向【|同步队列|】添加一个[独占模式Node](持有争锁线程)作为排队者
Node node = addWaiter(Node.EXCLUSIVE);
// 当node进入排队后再次尝试申请锁,如果还是失败,则可能进入阻塞
if(acquireQueued(node, arg)){
// 如果线程解除阻塞时拥有中断标记,此处要进行设置
selfInterrupt();
}
}
}
调用tryAcquire函数,调用此方法的线程会尝试申请独占锁。在AQS中该方法会抛异常,并没有实现,需要由子类实现。
如果失败,说明线程没有申请到独占锁,需要以结点形式加入到同步队列末尾
排队之后再次尝试申请锁,如果还是失败线程就阻塞
我们重点来看其中涉及到的方法:
尾插加入同步队列末尾
/*
* 向【|同步队列|】添加一个排队者(线程)
*
* mode有两种可能:
* 1.独占模式:Node.EXCLUSIVE
* 2.共享模式:Node.SHARED
* 由此,可创建[独占模式Node]或[共享模式Node]
* 创建的[模式Node]会记下当前线程的引用,并进入同步队列进行排队
*/
private Node addWaiter(Node mode) {
// 创建一个独占/共享模式的node,该node存储了当前线程的引用
Node node = new Node(mode);
// 使用尾插法将node添加到【|同步队列|】
enq(node);
// 返回刚加入【|同步队列|】的[模式Node]
return node;
}
// 使用尾插法将node添加到【|同步队列|】,并返回旧的队尾
private Node enq(Node node) {
for(; ; ) {
Node oldTail = tail;
if(oldTail != null) {
// 设置node的前驱为oldTail
node.setPrevRelaxed(oldTail);
// 更新队尾游标指向node
// oldTail为tail的引用,这里的CAS是把oldTail地址对应的值设置为node,相当于tail = node;
if(compareAndSetTail(oldTail, node)) {
// 链接旧的队尾与node,形成一个双向链表
oldTail.next = node;
return oldTail;
}
} else {
// 【|同步队列|】不存在时,需要初始化一个头结点
initializeSyncQueue();
}
}
}
进入同步队列后的结点尝试抢锁,只有当前结点是头节点才有抢锁资格。
如果失败,则会调用shouldParkAfterFailedAcquire
判断能否挂起线程,然后调用parkAndCheckInterrupt
挂起当前线程
// 当node进入排队后再次尝试申请锁,如果还是失败,则可能进入阻塞
final boolean acquireQueued(final Node node, int arg) {
// 记录当前线程从阻塞中醒来时的中断标记(阻塞(park)期间也可设置中断标记)
boolean interrupted = false;
try {
/*
* 死循环,成功申请到锁后退出
*
* 每个陷入阻塞的线程醒来后,需要重新申请锁
* 只有当自身排在队首时,才有权利申请锁
* 申请成功后,需要丢弃原来的头结点,并将自身作为头结点,然后返回
*/
for(; ; ) {
// 获取node结点的前驱
final Node p = node.predecessor();//返回AQS的prev
// 如果node结点目前排在了队首,则node线程有权利申请锁
if(p == head) {
// 再次尝试申请锁
if(tryAcquire(arg)){
// 设置node为头结点(即丢掉了原来的头结点)
setHead(node);
// 切断旧的头结点与后一个结点的联系,以便GC
p.next = null;
// 返回线程当前的中断标记(如果线程在阻塞期间被标记为中断,这里会返回true)
return interrupted;
}
}
// 抢锁失败时,尝试为node的前驱设置阻塞标记(每个结点的阻塞标记设置在其前驱上)
if(shouldParkAfterFailedAcquire(p, node)) {
/*
* 使线程陷入阻塞
*
* 如果首次到达这里时线程被标记为中断,则此步只是简单地清除中断标记,并返回true
* 接下来,通过死循环,线程再次来到这里,然后进入阻塞(park)...
*
* 如果首次到达这里时线程没有被标记为中断,则直接进入阻塞(park)
*
* 当线程被唤醒后,返回线程当前的中断标记(阻塞(park)期间也可设置中断标记)
*/
interrupted |= parkAndCheckInterrupt();
}
}
} catch(Throwable t) {
// 如果中途有异常发生,应当撤销当前线程对锁的申请
cancelAcquire(node);
// 如果发生异常时拥有中断标记,此处要进行设置
if(interrupted) {
selfInterrupt();
}
throw t;
}
}
当入队的结点抢锁失败之后,调用该函数判断线程是否应该挂起
/*
* 抢锁失败时,尝试为node的前驱设置阻塞标记
*
* 每个结点的阻塞标记设置在其前驱上,原因是:
* 每个正在活动的结点都将成为头结点,当活动的结点(头结点)执行完之后,
* 需要根据自身上面的阻塞标记,以确定要不要唤醒后续的结点
*
*/
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
// node前驱的状态
int ws = pred.waitStatus;
// 如果node前驱的状态为Node.SIGNAL,则表示node需要进入阻塞状态
if(ws == Node.SIGNAL){
/* This node has already set status asking a release to signal it, so it can safely park. */
// 只有前驱节点为SIGNAL才能调用park阻塞线程
return true;
}
// 如果node前驱的状态被标记为取消,则顺着其前驱向前遍历,将紧邻的待取消结点连成一片
if(ws>0) {
/* Predecessor was cancelled. Skip over predecessors and indicate retry. */
do {
node.prev = pred = pred.prev;
} while(pred.waitStatus>0);
pred.next = node;
} else {
/*
* waitStatus must be 0 or PROPAGATE.
* Indicate that we need a signal, but don't park yet.
* Caller will need to retry to make sure it cannot acquire before parking.
*/
// 更新node前驱的状态为Node.SIGNAL,即使node陷入阻塞
pred.compareAndSetWaitStatus(ws, Node.SIGNAL);
}
return false;
}
阻塞线程的方法封装在这个函数中
首先执行park操作,即禁用当前线程,然后返回该线程是否已经被中断
// 设置线程进入阻塞状态,并清除线程的中断状态。返回值代表之前线程是否处于阻塞状态
private final boolean parkAndCheckInterrupt() {
// 设置线程阻塞(对标记为中断的线程无效)
LockSupport.park(this);
return Thread.interrupted();//会清除中断标志位
}
如果acquireQueued抛异常,就要调用这个方法设置当前Node为CANCELLED,使其丧失获得资源的资格
// 标记node结点为Node.CANCELLED(取消)状态
private void cancelAcquire(Node node) {
// Ignore if node doesn't exist
if(node == null) {
return;
}
// 删除对线程的引用,方便GC
node.thread = null;
// Skip cancelled predecessors
Node pred = node.prev;
// 顺着node的前驱向前遍历,将标记为取消的node结点连成一片
while(pred.waitStatus>0) { //CANCELLED = 1
node.prev = pred = pred.prev;
}
// predNext is the apparent node to unsplice.
// CASes below will fail if not, in which case, we lost race vs another cancel or signal,
// so no further action is necessary, although with a possibility that a cancelled node may transiently remain reachable.
Node predNext = pred.next;
// Can use unconditional write instead of CAS here.
// After this atomic step, other Nodes can skip past us.
// Before, we are free of interference from other threads.
// 标记node线程进入取消状态
node.waitStatus = Node.CANCELLED;
// If we are the tail, remove ourselves.
// 如果当前节点是尾节点,把pred设置成tail
if(node == tail && compareAndSetTail(node, pred)) {
// 把predNext即当前Node设为null
pred.compareAndSetNext(predNext, null);
} else {
// If successor needs signal, try to set pred's next-link so it will get one.
// Otherwise wake it up to propagate.
int ws;
if(pred != head
&& ((ws = pred.waitStatus) == Node.SIGNAL
|| (ws<=0 && pred.compareAndSetWaitStatus(ws, Node.SIGNAL)))
&& pred.thread != null) {
Node next = node.next;
// 将处于阻塞状态的node连成一片,通过后继向后遍历即可获得
if(next != null && next.waitStatus<=0) {
//pred.next = next 相当于把node忽略掉
pred.compareAndSetNext(predNext, next);
}
} else {
// 唤醒node后面陷入阻塞的“后继”
unparkSuccessor(node);
}
node.next = node; // node后继指向自身,目的是为了便于GC,因为再也没有别的引用指向Node了
}
}
结合下面这张图来理解,cancelAcquire以Node为参数,把其状态设置为CANCELLED,并且向前遍历把直到找到SIGNAL的结点,中间CANCELLED的所有节点都会被移除这个同步队列。然后调用unparkSuccessor
尝试唤醒node的下一个正在阻塞的结点。
/*
* 唤醒node后面陷入阻塞的“后继”
*
* 注:
* 由于有些结点可能会被标记为Node.CANCELLED(取消),
* 所以这里的后继可能不是node.next,需要进一步搜索后才能确定
*/
private void unparkSuccessor(Node node) {
/*
* If status is negative (i.e., possibly needing signal) try to clear in anticipation of signalling.
* It is OK if this fails or if status is changed by waiting thread.
*/
int ws = node.waitStatus;
if(ws<0) {
// 如果node状态码为负,则将其重置为0
node.compareAndSetWaitStatus(ws, 0);
}
/*
* Thread to unpark is held in successor, which is normally just the next node.
* But if cancelled or apparently null, traverse backwards from tail to find the actual non-cancelled successor.
*/
Node s = node.next;
/*
* 如果s==null,说明node已经是尾结点,后面没有需要唤醒的结点了
*
* 如果s!=null,且s.waitStatus>0,说明node被标记为Node.CANCELLED(取消)
* 此时,需要从尾端向前遍历,找到离s最近的正处于阻塞的后继,以便后续唤醒它
*/
if(s == null || s.waitStatus>0) {
s = null;
for(Node p = tail; p != node && p != null; p = p.prev) {
if(p.waitStatus<=0) {
s = p;
}
}
}
// 唤醒node的后继
if(s != null) {
LockSupport.unpark(s.thread);
}
}
一个不允许中断带有中断标志的线程的acquire方法都那么复杂了…源码还有几个允许中断的方法,还有几个带超时参数的方法,以后再说吧,但是大概思路和上面是一样的
// 释放锁,如果锁已被完全释放,则唤醒后续的阻塞线程。返回值表示本次操作后锁是否自由
public final boolean release(int arg) {
// 释放一次锁,返回值表示同步锁是否处于自由状态(无线程持有)
if(tryRelease(arg)) {
/* 如果锁已经处于自由状态,则可以唤醒下一个阻塞的线程了 */
Node h = head;
if(h != null && h.waitStatus != 0) {
// 唤醒h后面陷入阻塞的“后继”
unparkSuccessor(h);
}
return true;
}
return false;
}
tryRelease在AQS中没有实现,需要依赖子类来实现
acquire和release方法相当于实现了锁中lock和unlock的操作
acquire总结起来就是当前线程尝试去抢占锁,如果抢不了就以Node形式加入到队列中,再尝试抢一次,抢不到就挂起阻塞
然后之后AQS具体怎么用,就要看其子类的源码的时候再给出相关源码了。至于contentObject方法得具体实现,可能要到看ReentrantLock和ReentrantReadWriteLock的时候再介绍了
https://juejin.im/post/5ae754af518825672a02b498
https://www.cnblogs.com/leesf456/p/5350186.html
https://editor.csdn.net/md/?articleId=106038954