ConditionObject
1.1 ConditionObject的介绍&使用
像synchronized提供了wait和notify的方法实现线程在持有锁时,可以实现挂起,已唤醒的操作
ReentrantLock也拥有这个功能。
ReentrantLock提供了await和signal方法实现类似的wait和notify的功能
想执行await或者是sinal就必须持有lock锁的资源
public class IConditionObject {
public static void main (String[] args) throws InterruptedException {
Lock lock = new ReentrantLock();
Condition condition = lock.newCondition();
new Thread(() -> {
lock.lock();
System.out.println(Thread.currentThread().getName() + " 获取到锁资源并await挂起线程并释放锁资源");
try {
Thread.sleep(10000);
condition.await();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println(Thread.currentThread().getName() + " 线程被唤醒,持有锁资源");
}, "lock").start();
Thread.sleep(1000);
lock.lock();
System.out.println(Thread.currentThread().getName() + " 拿到锁资源,子线程执行await方法");
condition.signal();
System.out.println(Thread.currentThread().getName() + " 执行 signal,主线程唤醒await的线程");
lock.unlock();
System.out.println("=====================================");
new Thread(() -> {
System.out.println(Thread.currentThread().getName() + " wait ~");
synchronized (IConditionObject.class){
try {
Thread.sleep(3000);
IConditionObject.class.wait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println(Thread.currentThread().getName() + " 被唤醒");
}
}, "sync").start();
Thread.sleep(1000);
synchronized (IConditionObject.class){
System.out.println(Thread.currentThread().getName() + " notify");
IConditionObject.class.notify();
}
}
}
1.2 Condition的构建方式&核心属性
发现在通过lock锁对象执行newCondition方法时,本质就是直接new的AQS提供的ConditionObject对象
public Condition newCondition() {
return sync.newCondition();
}
其实lock锁中可以有多个Condition对象
在对Condition1进行操作时,不会影响到Condition2的单向链表
你可以发现ConditionObject中,只有两个核心属性
/** First node of condition queue. */
private transient Node firstWaiter;
/** Last node of condition queue. */
private transient Node lastWaiter;
虽然Node对象都有prev和next,但是在ConditionObject中式不会使用这两个属性的,只要在Condition队列中,这两个属性都是null,在ConditionObject中只会使用nextWaiter的属性实现单向链表的效果
1.3 Condition的await方法分析
持有锁的线程在执行await方法后会做几个操作
- 判断线程是否中断,如果中断了,什么都不做
- 没有中断,就将当前线程封装为Node添加到Condition的单向链表中
- 一次性释放掉锁资源
- 如果当前线程没有在AQS队列,就正常执行LockSuppot.park(this)挂起线程
- 唤醒之后,要先确认式中断唤醒还是signal唤醒,还是signal唤醒后被中断唤醒
- 确保当前线程的Node已经在AQS中
- 执行acquireQueued方法,等到锁资源
- 在获取锁资源后,要确认是否在获取锁资源的阶段被中断过,如果被中断过,并且不是THROW_IE,那就确保interruptMode式REINTERRUPT。
- 确认当前Node已经不在COndition队列中了
- 最终根据interruptMode来决定具体做什么事情
- 0:正常,
- THROW_IE(-1):抛出异常
- REINTERRUPT(1):执行线程的interrupt方法
- await()
public final void await() throws InterruptedException {
// 判断线程中断标记是否为true
if (Thread.interrupted())
// 中断了抛出异常
throw new InterruptedException();
// 在挂起线程之前,先将当前线程封装为Node,并且添加到Condition队列中
Node node = addConditionWaiter();
// fullyRelease释放锁子资源,一次性将锁资源释放干净,并且保留重入次数
int savedState = fullyRelease(node);
// 中断模式~
int interruptMode = 0;
while (!isOnSyncQueue(node)) {
LockSupport.park(this);
// 如果线程执行到这,说明现在被唤醒了
// 线程可以被signal唤醒,(如果是signal唤醒,可以确认线程已经在AQS队列中)
// 线程可以被interrupt唤醒,线程被唤醒后,没有在AQS队列中
// 如果线程先被signal唤醒,然后线程中断了
// checkInterruptWhileWaiting可以确认当前中如何唤醒的
// 返回的值,有三种
// 0:正常signal唤醒,没别的事情
// THROW_IE(-1): 中断唤醒,并且可以确保在AQS中
// REINTERRUPT(1): siganl唤醒,但是线程被中断了,并且可以确保在AQS中
if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
break;
}
// Node一定在AQS队列
// 执行acquireQueued,尝试在ReentrantLock中获取锁资源
// acquireQueued返回true,代表线程在AQS中挂起时,被中断过
if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
// 如果线程在AQS队列排队时,被中断过,并且不是THROW_IE状态,确保线程interruptMode是REINTERRUPT
interruptMode = REINTERRUPT;
// 如果当前Node还在Condition单向链表中,需要脱离单向链表
if (node.nextWaiter != null) // clean up if cancelled
unlinkCancelledWaiters();
// 如果不是0
if (interruptMode != 0)
reportInterruptAfterWait(interruptMode);
// 如果interruptMode是0,说明线程在signal后以及持有锁的过程中,没被中断过,什么事都不做
}
- addConditionWaiter()
// 线程挂起之前,添加到Condition单向链表中
private Node addConditionWaiter() {
// 拿到尾节点
Node t = lastWaiter;
// 如果尾节点有值,并且不是挂起的状态,则要清理尾节点
if (t != null && t.waitStatus != Node.CONDITION) {
// 如果尾节点已经取消,需要干掉取消的尾节点
unlinkCancelledWaiters();
// 重新获取尾节点
t = lastWaiter;
}
// 构建当前线程Node,并且状态设置-2,挂起状态
Node node = new Node(Thread.currentThread(), Node.CONDITION);
// 如果尾节点为null,直接将当前节点设置为头节点
if (t == null)
firstWaiter = node;
else
// 尾节点不是null,说明链表中有值,将当前节点挂在尾节点的后面
t.nextWaiter = node;
// 把当前节点设置为最后一个节点
lastWaiter = node;
// 当前节点返回
return node;
}
- unlinkCancelledWaiters() 清理掉取消的节点
// 清理掉取消的尾节点
private void unlinkCancelledWaiters() {
// 获取到头节点
Node t = firstWaiter;
// 临时指针
Node trail = null;
// 遍历链表
while (t != null) {
// 拿到t的next节点
Node next = t.nextWaiter;
// 判断t的状态不为-2,挂起的状态,说明有问题
if (t.waitStatus != Node.CONDITION) {
// 就是把状态不是挂起的状态的节点跳过,移除,重新编制链表
// t的next指针指向null
t.nextWaiter = null;
// trail 为null,代表头节点状态不是挂起的状态
if (trail == null)
// 将头节点指向next节点
firstWaiter = next;
else
// trail 有值,说明不是头节点位置
// 跳过有问题节点
trail.nextWaiter = next;
// next 为null说明,链表遍历到最后,直接位数
if (next == null)
lastWaiter = trail;
}
// 如果t的状态式-2,一切正常
else
// trail 临时存储t,上一个指针
trail = t;
// 指针往后跳
t = next;
}
}
- fullyRelease(Node node) 一次性释放锁资源
final int fullyRelease(Node node) {
// 标记位,释放锁资源默认失败
boolean failed = true;
try {
// 拿到线程的state值
int savedState = getState();
// 一次性释全部锁资源
if (release(savedState)) {
// 标记锁没有释放失败
failed = false;
// 返回锁重入几次信息
return savedState;
} else {
throw new IllegalMonitorStateException();
}
} finally {
if (failed)
// 如果锁资源失败了,讲节点状态设置为取消状态
node.waitStatus = Node.CANCELLED;
}
}
- boolean isOnSyncQueue(Node node) 确认Node是否在AQS队列上
// 确认Node是否在AQS队列上
final boolean isOnSyncQueue(Node node) {
// 线程的状态为-2,肯定没有在AQS队列
// 如果prev节点为null,肯定没有在AQS队列
if (node.waitStatus == Node.CONDITION || node.prev == null)
// 返回false,没有在AQS队列中
return false;
// 如果节点的next不为null,说明已经在AQS队列上
if (node.next != null)
// AQS队列上有
return true;
// 如果上述判断没有确认节点是否在AQS队列上,在AQS队列中寻找一波
return findNodeFromTail(node);
}
- boolean findNodeFromTail(Node node) 在AQS队列中找到当前节点
// 在AQS队列中找到当前节点
private boolean findNodeFromTail(Node node) {
// 拿到尾节点
Node t = tail;
for (;;) {
// tail是否式当前节点,如果是,说明在AQS队列
if (t == node)
// 结束循环,在AQS中
return true;
// 如果节点为null,AQS队列中没有当前节点
if (t == null)
// 结束循环,不在AQS中
return false;
// t 往前跳
t = t.prev;
}
}
- int checkInterruptWhileWaiting(Node node) 检查是否有中断
// 检查是否有中断,如果在发出信号之前中断,则返回THROW_IE,
// 如果在收到信号之后中断,则重新中断,如果没有中断,则为0。
private int checkInterruptWhileWaiting(Node node) {
// 判断线程是否中断了
return Thread.interrupted() ?
// THROW_IE:代表这线程是interrupt唤醒的
// REINTERRUPT:代表线程是signal唤醒的,但是在唤醒后,中断了
(transferAfterCancelledWait(node) ? THROW_IE : REINTERRUPT) :
// 0 线程是正常被signal唤醒,并且线程没有中断过
0;
}
- boolean transferAfterCancelledWait(Node node) 判断线程是中断唤醒的,还是signal唤醒的
// 判断线程是中断唤醒的,还是signal唤醒的
final boolean transferAfterCancelledWait(Node node) {
// 基于CAS将Node的状态从-2改为0
if (compareAndSetWaitStatus(node, Node.CONDITION, 0)) {
// 说明是中断唤醒的,因为CAS成功了
// 将Node添加到AQS队列中~(如果是中断唤醒,当前线程同时存在Condition链表和AQS链表中)
enq(node);
// 返回true,中断唤醒
return true;
}
// 判断当前的Node是否在AQS中(signal唤醒的,但是可能线程还没有放到AQS队列中)
// 等到signal方法将线程的Node扔到AQS队列后,在做后续操作
while (!isOnSyncQueue(node))
// 如果没有在AQS队列上,那就线程让步,稍等一会,Node放到AQS队列在处理
Thread.yield();
// signal唤醒的,返回false
return false;
}
- reportInterruptAfterWait(int interruptMode) 判断中断模型
private void reportInterruptAfterWait(int interruptMode)
throws InterruptedException {
// 如果中断唤醒的await,抛出异常
if (interruptMode == THROW_IE)
throw new InterruptedException();
// 如果是REINTERRUPT,signal后被中断过
else if (interruptMode == REINTERRUPT)
// 确认线程的中断标记位是true Thread.currentThread().interrupt();
selfInterrupt();
}
1.4 Condition的signal方法分析
- 确保执行signal方法的是持有锁的线程
- 脱离Condition的队列
- 将Node状态从-2改为0
- 将Node添加到AQS队列
- 为了避免当前Node无法在AQS队列正常唤醒做了一些判断和操作
- signal()
// 线程挂起后,可以基于signal唤醒
public final void signal() {
// 在ReentrantLock中,如果执行了signal的线程没有持有锁资源,直接抛出异常
if (!isHeldExclusively())
throw new IllegalMonitorStateException();
// 拿到排在Condition首位的Node
Node first = firstWaiter;
// 有Node在排队,才需要唤醒
if (first != null)
doSignal(first);
}
- doSignal(Node first) 开始唤醒Condition中的Node的线程
// 开始唤醒Condition中的Node的线程
private void doSignal(Node first) {
do {
// 获取到第二个节点,并且将第二个节点设置为头节点
if ( (firstWaiter = first.nextWaiter) == null)
// 说明就一个节点在Condition队列中,那么直接将firstWaiter,lastWaiter设置为空
lastWaiter = null;
// 如果还有nextWaiter节点,以为当前节点要被唤醒了,脱离整个Condition队,将nextWaiter设置为空
first.nextWaiter = null;
// transferForSignal返回true,一切正常,推出while循环
} while (!transferForSignal(first) &&
// 如果后续节点还有,往后面继续唤醒,如果没有结束循环
(first = firstWaiter) != null);
}
- boolean transferForSignal(Node node) 准备开始唤醒在Condition中排队的Node
// 准备开始唤醒在Condition中排队的Node
final boolean transferForSignal(Node node) {
// 将在Condition队列中的Node状态从-2,改为0,代表要扔到AQS队列中
if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
// 如果失败了,说明在signal之前应当是线程被中断了,从而被唤醒了。
return false;
// 如果正常从-2改为0,这就是要从Condition队列扔到AQS中去
// 将当前Node扔到AQS队列,返回的p是当前Node的prev
Node p = enq(node);
// 前继节点的状态
int ws = p.waitStatus;
// 如果前继节点状态不是取消的状态
// 将前继节点状态改为-1
if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
// 如果prev节点已经取消了,可能导致当前节点永远不会被唤醒,立即唤醒当前节点
// 基于acquireQueued方法,让当前节点找到一个正常前继节点,并挂起线程
// 如果prev节点正常,但是CAS修改prev节点失败了。证明prev节点因为并发原因导致状态改变。
// 还是为了避免当前节点无法被正常唤醒,提前唤醒当前线程,基于acquireQueued方法,让当前节点找到一个正常的prev节点,并挂起线程
LockSupport.unpark(node.thread);
// 返回true
return true;
}
1.5 Condition的awaitNanos&signalAll方法分析
awaitNanos:仅仅是在await方法的基础上,做了一些的改变,整体的逻辑思想都是一样的。
挂起来线程时,传入要阻塞的时间,时间到了,自动唤醒,走添加到AQS队列的逻辑
// await指定时间,时间到了自动醒
public final long awaitNanos(long nanosTimeout)
throws InterruptedException {
// 判断是否中断
if (Thread.interrupted())
// 如果中断了抛出异常
throw new InterruptedException();
// 添加到Condition单向链表中
Node node = addConditionWaiter();
// 一次性释放掉锁
int savedState = fullyRelease(node);
// 计算什么时间结束
final long deadline = System.nanoTime() + nanosTimeout;
// 中断模型
int interruptMode = 0;
while (!isOnSyncQueue(node)) {
// nanosTimeout的时间小于等0,直接结束
if (nanosTimeout <= 0L) {
// 尝试从等待状态修改成0,并加入到AQS队列中
transferAfterCancelledWait(node);
break;
}
// nanosTimeout的时间大于1000纳米时,才可以挂起
if (nanosTimeout >= spinForTimeoutThreshold)
// 大于,正常挂起线程
LockSupport.parkNanos(this, nanosTimeout);
// 检查节点是否被中断唤醒
if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
break;
// 计算出剩余挂起时间
nanosTimeout = deadline - System.nanoTime();
}
if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
interruptMode = REINTERRUPT;
if (node.nextWaiter != null)
unlinkCancelledWaiters();
if (interruptMode != 0)
reportInterruptAfterWait(interruptMode);
return deadline - System.nanoTime();
}
signalAll方法,这个方法一看就懂,之前signal是唤醒1个,这个是全部唤醒
// do-while的形式,将Condition单向链表中的所有Node,全部唤醒并扔到AQS队列中
private void doSignalAll(Node first) {
// 将头尾节点都只为空
lastWaiter = firstWaiter = null;
do {
// 拿到next节点的引用
Node next = first.nextWaiter;
// 断开当前Node的nextWaiter
first.nextWaiter = null;
// 修改Node状态,扔到AQS队列,是否唤醒
transferForSignal(first);
// 指向下一个节点
first = next;
} while (first != null);
}