流程图
await()流程图
await()节点出队重新构造入队流程图
signal流程图
signal节点迁移示意图
1.Condition接口
public interface Condition {
//阻塞 可以响应中断。
void await() throws InterruptedException;
//阻塞 不响应中断
void awaitUninterruptibly();
//可以指定挂起时间
long awaitNanos(long nanosTimeout) throws InterruptedException;
boolean await(long time, TimeUnit unit) throws InterruptedException;
boolean awaitUntil(Date deadline) throws InterruptedException;
//唤醒条件队列中第一个可以被唤醒的节点(将等待队列的节点迁移到同步队列中)
void signal();
//唤醒条件队列中的所有节点全部迁移到同步队列中
void signalAll();
}
2.实现类ConditionObject
/*
* 此类是AQS中的一个内部类,等待队列使用的节点也是AQS的内部类 Node.
* 只不过与AQS的队列不同的是,等待队列中是单向链表。AQS的队列是双向链表。
* 注意 普通内部类是可以访问到类外部的域,(后面要做节点的迁移)
*/
public class ConditionObject implements Condition, java.io.Serializable {
//条件队列的头节点
private transient Node firstWaiter;
//条件队列的尾节点
private transient Node lastWaiter;
// lock.newCondition() 最终调用的还是这个构造器
public ConditionObject() {
}
3.await()方法
下面简称AQS的队列为同步队列
,Condition的队列(单向链表)下面简称为等待队列
public final void await() throws InterruptedException {
//判断当前线程是否是中断状态,如果是则直接抛出中断异常。。
if (Thread.interrupted())
throw new InterruptedException();
/*
* addConditionWaiter()将当前线程封装成Node节点加入条件队列中
* 并返回封装完成的Node节点 (类似AQS中的addWaiter方法)
*/
Node node = addConditionWaiter();
/*
* fullyRelease() 完全释放当前线程的锁(state)
* 因为这里要挂起等待被唤醒,所以必须先完全释放锁。
*/
long savedState = fullyRelease(node);
/*
* 0 表示在Condition 队列挂起期间没有接收过中断信号
* -1 在Condition队列挂起期间接收到了中断信号 (THROW_IE)
* 1 在Condition队列挂起期间接未收到了中断信号,但是迁移到"阻塞(同步)队
* 列AQS的队列"之后,接收过中断信号 (REINTERRUPT)
*/
int interruptMode = 0;
/*
* isOnSyncQueue(node)
* true表示当前节点已经迁移到了同步队列中
* false表示当前节点还在等待队列中如果当前节点还在等待队列中,则需要继续挂起。
*/
while (!isOnSyncQueue(node)) {
//当前节点还在等待队列中,继续被挂起。
LockSupport.park(this);
/*
* 这里就是唤醒后判断是否是被中断唤醒的,然后执行响应的中断逻辑。
*
* checkInterruptWhileWaiting()就算在等待队列挂起期间,
* 线程发生了中断,对应的node也会被迁移到同步队列(参考transferAfterCancelledWait)
* 如果当前node被中断过,也直接break。
*
* 什么时候会被唤醒?
* 1.常规:外部线程获取锁后,调用了signal()方法,转移条件队列的头节点,
* 到同步队列,当这个节点获取锁后,会被唤醒
* 2.转移到同步队列后,发现同步队列的前驱节点状态时取消状态,此时会直接唤醒当前节点
* 3.当前节点挂起期间,被外部线程使用中断唤醒。。
*/
if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
break;
}
/*
* 到这里 说明当前node已经被迁移到了同步队列中
*/
/*
* 1. acquireQueued()就是在同步队列中竞争锁的逻辑
* 返回true -> 表示在同步队列中被外部线程中断唤醒过
* 返回false -> 表示在同步队列中没有被外部线程中断唤醒过
* 2. 条件二成立表示没有在等待队列发生过中断
*
* 两个条件同时成立,表示node在同步队列发生过中断。
* 所以将interruptMode设置为REINTERRUPT(在同步队列发生过中断)
*/
if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
interruptMode = REINTERRUPT;
//考虑下 node.nextWaiter != null, 条件什么时候成立呢?
//其实是node在条件队列内时如果被外部线程中断唤醒时,会加入到阻塞队列
//但是并为设置nextWaiter = null. 这里需要做一个清理工作。
if (node.nextWaiter != null)
//清理等待队列内取消状态的节点。
unlinkCancelledWaiters();
/*
* 条件成立:说明挂起期间发生过中断 1.条件队列的挂起 2.条件队列之外的挂起
*/
if (interruptMode != 0)
reportInterruptAfterWait(interruptMode);
}
3.1addConditionWaiter
/*
* 将当前线程封装成一个Node加入到等待队列的末尾。
* (会将等待队列中所有处于取消状态的节点全部出队)
*/
private Node addConditionWaiter() {
//指向队尾
Node t = lastWaiter;
/*
* 队列不为空 并且 队尾的节点状态不是Condition(-2)而处于取消状态(cancel)
* 此时当前线程在队列中做一次清除处理,将所有处于取消状态的node全部干掉。
*/
if (t != null && t.waitStatus != Node.CONDITION) {
unlinkCancelledWaiters();
//最终t指向的还是尾结点(t可能会更新)
t = lastWaiter;
}
//将当前线程封装为一个Node,Node状态(waitStatus)为Condition(-2)
Node node = new Node(Thread.currentThread(), Node.CONDITION);
//下面就是入队的过程 (这里只有加锁的线程可以进来,所以不需要加锁)
if (t == null)
firstWaiter = node;
else
t.nextWaiter = node;
lastWaiter = node;
//最终返回构造的节点
return node;
}
3.2fullyRelease()
final long fullyRelease(Node node) {
boolean failed = true;
try {
//获取当前的state的值
long savedState = getState();
/*
* 全部释放掉state,变为初始状态0 这里正常情况下都会成功。
* (release方法见AQS源码解析系列博客)
*/
if (release(savedState)) {
failed = false;
/*
* 这里最终要返回加锁前的状态,因为执行到这时,构造出来的Node
* 节点还在等待队列中,当迁移到同步队列时还需要进行获取锁释放锁的逻辑
* 所以这里需要记录下初始锁的状态并返回。
*/
return savedState;
} else {
throw new IllegalMonitorStateException();
}
} finally {
if (failed)
node.waitStatus = Node.CANCELLED;
}
}
3.3isOnSyncQueue()
/*
* 判断当前Node是否在同步队列中
*/
final boolean isOnSyncQueue(Node node) {
/*
* 条件一: node.waitStatus == Node.Condition (-2)条件成立,说明当前Node
* 一定在等待队列,因为signal方法迁移节点到同步队列前,会将node的状态设置为0
*
* 条件二: 前置条件 node.waitStatus != Node.Condition
* 1.此时node.waitStatus 可能= 0 (表示当前节点已经被signal了)
* 2.此时node.waitStatus 可能= 1(取消) (当前线程为持有锁调用await方法,最终会将node的状态改为取消状态)
*
* node.waitStatuas == 0 为什么还要判断 node.prev == null?
* 因为signal是先修改状态 在迁移
* 因为等待队列的Node是单向链表,node.prev==null说明一定是在等待队列中
*/
if (node.waitStatus == Node.CONDITION || node.prev == null)
//直接return false。
return false;
/*
* 执行到这里,会使哪种情况?
* node.waitStatus != Condition 且 node.prev != null
* 可以排除 node.waitStatus == 1取消状态,
* 为什么可以排除? 因为signal不会把取消状态的node迁移走
*
* 设置prev引用的逻辑 是 迁移到同步队列设置的(enq()自旋入队)
* 入队的逻辑:
* 1.设置node.prev = tail;
* 2.CAS设置当前node为tail,成功才算正在进入到同步队列
* 3.pred.next = node;
* 就算prev不为null,也不能推算出当前node已经成功入队到同步队列了,(CAS成功才算)
*/
/*
* next不为null,说明当前节点已经成功入队到同步队列了,且当前节点后面已经有其他node了。。。
*/
if (node.next != null)
return true;
/*
* 执行到这里,说明当前节点的状态为 node.prev != null && node.witStatus = 0
* 然后从同步队列中遍历查找node,成功返回ture,失败返回false。
*
* 当前node有可能在signal过程中,正在迁移中。。还未完成
*/
return findNodeFromTail(node);
}
3.4.checkInterruptWhileWaiting()
private int checkInterruptWhileWaiting(Node node) {
/*
* 检查线程是否被中断?
* 被中断过就去判断是在那个队列中被中断的。最终返回-1 / 1
* 没有被中断过直接返回0.
*/
return Thread.interrupted() ?
(transferAfterCancelledWait(node) ? THROW_IE : REINTERRUPT) :
0;
}
3.5.transferAfterCancelledWait()
/*
* @return true 表示node是在
* false 表示node是在
*/
final boolean transferAfterCancelledWait(Node node) {
/*
* 条件成立: 说明当前的node一定是在等待队列中被中断唤醒的,因为
* signal迁移节点到同步队列时,会将节点的状态修改为0
*/
if (compareAndSetWaitStatus(node, Node.CONDITION, 0)) {
//在等待队列中中断唤醒的node也会被迁移到同步队列中
enq(node);
//直接return true。
return true;
}
/*
* 执行到这里有几种情况?
* 1.当前node已经被外部线程调用signal 方法将其迁移到 同步队列了
* 2.当前node正在被外部线程调用signal 方法将其迁移到同步队列 进行中。。。
* (因为将node从等待队列迁移到同步队列时会先将状态改为0)
*/
while (!isOnSyncQueue(node))
Thread.yield();
//这里表示node是在同步队列中被中断的。
return false;
}
3.6unlinkCancelledWaiters
/*
* 将等待队列中的所有取消状态的node全部出队。
*/
private void unlinkCancelledWaiters() {
//循环当前节点
Node t = firstWaiter;
//当前链表上一个正常状态的node节点
Node trail = null;
//遍历
while (t != null) {
Node next = t.nextWaiter;
//当前节点t状态不正常
if (t.waitStatus != Node.CONDITION) {
//直接
t.nextWaiter = null;
if (trail == null)
firstWaiter = next;
else //直接将t删除
trail.nextWaiter = next;
if (next == null)
lastWaiter = trail;
}
else
trail = t;
t = next;
}
}
3.7reportInterruptAfterWait
private void reportInterruptAfterWait(int interruptMode)
throws InterruptedException {
//条件成立: 说明在条件队列内发生过中断,此时await方法抛出中断异常
if (interruptMode == THROW_IE)
throw new InterruptedException();
//条件成立:说明在条件队列外发生的中断,此时设置当前线程的中断标记为为 true
//中断处理 交给业务
else if (interruptMode == REINTERRUPT)
selfInterrupt();
}
4.signal()唤醒后的逻辑
public final void signal() {
//判断当前线程是否是持有锁的线程
if (!isHeldExclusively())
throw new IllegalMonitorStateException();
//获取条件队列的第一个node
Node first = firstWaiter;
//不为NULL,则将第一个节点进行迁移到同步队列的逻辑
if (first != null)
doSignal(first);
}
4.1doSignal
private void doSignal(Node first) {
//自旋
do {
/*
* 当前节点要进行出队,所以更新firstWaiter的指向
* 如果当前等待队列只有一个节点的话,也更新lastWaiter。
*/
if ( (firstWaiter = first.nextWaiter) == null)
lastWaiter = null;
first.nextWaiter = null;
/*
* transferForSignal(first) true->迁移成功 false->迁移失败
* 这里表示要么迁移一个节点成功 要么 队列为空跳出循环。
*/
} while (!transferForSignal(first) &&
(first = firstWaiter) != null);
}
4.2AQS.transferForSignal
/*
* @return true表示成功迁移 false表示没有成功迁移
*/
final boolean transferForSignal(Node node) {
/*
* CAS修改的当前Node的状态为0,因为马上node要迁移到同步队列了,
* 如果CAS失败,说明当前Node已经被取消了,直接返回false即可。
* (线程await时,未持有锁,最终线程对应的node会设置为取消状态)
* (node对应的线程,挂起期间,被其他线程使用中断信号唤醒过。。会主动进入同步队列
* 并且将状态设置为0)
*/
if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
return false;
/*
* enq() 自旋进入同步队列,一定会入队成功,
* 最终返回当前node进入同步队列后的前驱节点
*
* enq()方法参考AQS源码解析
*/
Node p = enq(node);
//获取前驱节点的waitStatus。
int ws = p.waitStatus;
/*
* 1.判断如果前驱节点的状态 > 0(取消),那么直接唤醒node。
* 2.前驱节点的node <= 0,那么尝试CAS修改为-1(signal),即前驱节点释放锁后
* 唤醒后继节点。
* 设置失败说明前驱节点的状态突然被取消了,需要唤醒后继节点。
* 当前驱node对应的线程是lockInterrupt入队时的node,是会响应中断的,外部
* 线程给前驱node中断信号之后,前驱node会将状态修改为取消状态,并且执行出队逻辑
*/
if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
//走到这里 说明前驱节点状态处于取消状态,那么直接唤醒node。
LockSupport.unpark(node.thread);
return true;
}