Condition概述:
Java对象有一组监视器方法:wait,notify,而synchronized本身就是利用虚拟机提供的对象监视器(objectMonitor)实现同步,这些方法与synchronized配合实现了等待和通知模式,Conditon提供了类似synchronized监视器的方法,利用AQS条件队列与Lock配合实现了等待和通知模式;
Condition内部是AQS的内部类利用条件队列实现阻塞和通知线程的效果;当一个线程在调用了await方法以后会被阻塞,调用signal方法唤醒,这种方式为线程提供了简单的等待和通知模式;一个Conditon的实例必须与一个Lock绑定,因为Condition是作为AQS的内部类实现的,而Lock内部器时就是使用AQS实现的同步功能,因此Condition作用需要与Lock或者实现了AQS的类配合使用;
常用的方法:
1.await方法:造成当前线程在接收信号或者中断之前处于的等待状态;
2.await(long time,TimeUnit unit)方法:当前线程在接收到信号,被中断或者到达等待时间之前一直处于等待状态;
3.awaitNanos(long nanosTimeout):当前线程在接收到信号,被中断或者到达指定等待时间之前一直处于等待状态;返回值表示剩余时间,如果在nanosTimeout之前唤醒,那么返回值为nanosTimeout - 消耗时间,如果返回值<=0,则认定为已经超时了;
4.awaitUninterruptibly方法:造成当前线程在接收信号之前一直处于等待状态,该方法对中断不敏感;
5.awaitUntil(Date deadLine):造成当前线程在接收信号,被中断或者到达指定最后期限之前一直处于等待状态;如果没有到达指定时间就被通知,则返回true,否则表示到达指定时间,返回false;
6.singal方法:唤醒一个等待线程;该线程从等待状态返回前必须获取与Condition相关联的锁;
7.singalAll方法:唤醒所有的等待线程。能够从等待方法返回的线程获取与Condition相关联的锁;
等待机制:
Condition是AQS的内部类,每个Condition对象都包含一个队列(等待队列); 等待队列的是一个FIFO的队列,在队列中的每个节点都包含了一个线程的引用,该线程就是在Condition对象上等待的线程,如果一个线程调用了Condition.await方法,该线程将会释放锁,以当前线程构造成节点并将该节点从尾部添加到等待队列,如果不是通过其他线程调用Conditon.singal方法唤醒,而是对等待线程进行中断interrupt方法,则会抛出InterruptedException异常信息;
通知机制:
调用Condition的singal方法,将会唤醒在等待队列上首节点,在唤醒节点前,会将节点移动到同步队列,在调用singal方法之前必须判断是否获取到了锁;接着获取等待队列的首节点,将其移动到同步队列并利用LockSupport唤醒节点的线程;
Condition源码分析:
等候机制:
等候机制的操作分为如下几步:
1.调用addConditionWaiter方法,将节点添加到等候队列
2.调用fullyRelease方法,释放锁机制
3.while循环,判断当前节点是否在同步队列中,如果不在则挂起当前线程
4.获取锁,并判断线程是否中断
public final void await() throws InterruptedException {
//这里表明await()方法对中断敏感,线程中断为true时调用await()就会抛出异常
if (Thread.interrupted())
throw new InterruptedException();
//将当前线程封装成节点并且设置为CONDITION加入到Condition队列中去,这里如果lastWaiter不为CONDITION状态,那么会把它踢出Condition队列。
Node node = addConditionWaiter();
//释放node节点线程的锁
int savedState = fullyRelease(node);
int interruptMode = 0;
//判断节点是否在同步队列中,在则使用LockSupport.park将其挂起
while (!isOnSyncQueue(node)) {
LockSupport.park(this);
if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
break;
}
if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
interruptMode = REINTERRUPT;
if (node.nextWaiter != null) // clean up if cancelled
unlinkCancelledWaiters();
if (interruptMode != 0)
reportInterruptAfterWait(interruptMode);
}
1.将当前线程封装为节点并设置为CONDITION加入到Condition队列中,这里如果lastWaiter不为CONDITION状态,那么会把它踢出Condition队列;
private Node addConditionWaiter() {
Node t = lastWaiter;
// 遍历队列,将状态不为CONDITION的节点剔除出队列
if (t != null && t.waitStatus != Node.CONDITION) {
unlinkCancelledWaiters();
t = lastWaiter;
}
//将当前线程封装成节点并且设置为CONDITION加入到Condition队列中去
Node node = new Node(Thread.currentThread(), Node.CONDITION);
//尾节点为空则将节点表明队列为空,将新节点设置为头节点
if (t == null)
firstWaiter = node;
else
//尾节点不为空则将节点表明队列不为空,将新节点设置为尾节点的后续节点
t.nextWaiter = node;
//将新节点设置为尾节点
lastWaiter = node;
return node;
}
2.使用release确保线程释放:
final int fullyRelease(Node node) {
boolean failed = true;
try {
//获取到锁代表getState()大于0
int savedState = getState();
//这里会确保线程释放
if (release(savedState)) {
failed = false;
return savedState;
} else {
throw new IllegalMonitorStateException();
}
} finally {
if (failed)
node.waitStatus = Node.CANCELLED;
}
}
3.release利用tryRelease先进行释放锁,tryRealse是ReentrantLock继承AQS实现的方法,可以确保线程是获取到锁的,并进行释放锁,unparkSuccessor主要是利用LockSupport.unpark唤醒线程;
public final boolean release(int arg) {
//释放锁,这个方法是ReentrantLock继承AQS实现的方法
if (tryRelease(arg)) {
Node h = head;
//如果节点状态不是CANCELLED,也就是线程没有被取消,也就是不为0的,就进行唤醒
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}
4.这个方法主要是确保了当前线程持有的锁,不是抛出异常,确保线程一定获取到锁的线程,并进行响应的释放锁:
protected final boolean tryRelease(int releases) {
//将线程的state计时器减-1,state为0代表没有线程持有锁,大于0则代表有线程持有锁了
int c = getState() - releases;
//判断是否是当前线程持有的锁,不是则抛出异常,确保线程一定是获取到锁的线程
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
if (c == 0) {
free = true;
//将记录持有线程的变量置为空
setExclusiveOwnerThread(null);
}
setState(c);
return free;
}
5.isOnSyncQueue主要使用于判断node是否在同步队列中:
final boolean isOnSyncQueue(Node node) {
//判断节点的状态,如果状态是CONDITION,说明节点肯定不在同步队列中,同时哪怕同步队列是刚刚初始化的,也会有一个冗余的头节点存在,
//所以节点的前驱节点如果为null,那么节点也肯定不在同步队列中,返回fasle
if (node.waitStatus == Node.CONDITION || node.prev == null)
return false;
//节点的后继节点不为null,说明节点肯定在队列中,返回true,
//这里很重要的一点要明白,prev和next都是针对同步队列的节点
if (node.next != null)
return true;
//调用findNodeFromTail,查找node是否在同步队列中
return findNodeFromTail(node);
}
signal机制:
signal的作用就是将await中Condition队列的第一个节点唤醒;
public final void signal() {
//isHeldExclusively是需要子类继承的,在lock中判断当前线程是否是获得锁的线程,是则返回true,如何当前线程不是获取锁的线程则抛出异常
if (!isHeldExclusively())
throw new IllegalMonitorStateException();
//获取Condition队列中第一个Node
Node first = firstWaiter;
//判断Condition队列是否为空
if (first != null)
doSignal(first);
}
1.
private void doSignal(Node first) {
do {
//头节点为空则,队列为空
if ( (firstWaiter = first.nextWaiter) == null)
lastWaiter = null;
//将头结点从等待队列中移除
first.nextWaiter = null;
} while (!transferForSignal(first) &&
(first = firstWaiter) != null);
}
2.transferForSignal把头节点发送到同步队列中,然后使用LockSupport.unpark唤醒节点线程:
final boolean transferForSignal(Node node) {
//通过CAS将状态为CONDITION节点的状态修改为0
if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
return false;
//将该节点移入到同步队列中去,p是node的前缀节点
Node p = enq(node);
int ws = p.waitStatus;
//以下情况进行唤醒节点
//1、node的前缀节点状态为0或者节点状态不为零
//2、node的前缀节点状态修改为SIGNAL状态失败
if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
LockSupport.unpark(node.thread);
return true;
}
分析:
1.首先判断当前线程是否为获取锁的线程
2.将等待队列的头节点移动到同步队列
参考文档:
添加链接描述