文章目录
一、:Condition
①Condition简介
Condition是JUC提供的与Java的Object中wait/notify/notifyAll类似功能的一个接口,通过此接口,线程可以在某个特定的条件下等待/唤醒。
与wait/notify/notifyAll操作需要获得对象监视器类似,一个Condition实例与某个互斥锁绑定,在此Condition实例进行等待/唤醒操作的调用也需要获得互斥锁,线程被唤醒后需要再次获取到锁,否则将继续等待。
而与原生的wait/notify/notifyAll等API不同的地方在于:JUC提供的Condition具有更丰富的功能,例如等待可以响应/不响应中断,可以设定超时时间或是等待到某个具体时间点。此外,一把互斥锁可以绑定多个Condition,这意味着在同一把互斥锁上竞争的线程可以在不同的条件下等待,唤醒时可以根据条件来唤醒线程,这是Object中的wait/notify/notifyAll不具备的机制。
参照Object的wait和notify/notifyAll方法,Condition也提供了同样的方法:
针对Object的wait方法:
●void await() throws InterruptedException:当前线程进入等待状态,如果其他线程调用condition的signal()或者signalAll()方法并且当前线程获取Lock,从await()方法返回。如果在等待状态中被中断会抛出被中断异常。
●long awaitNanos(long nanosTimeout):当前线程进入等待状态直到被通知、中断或者超时。
●boolean await(long time, TimeUnit unit)throws InterruptedException:同上一个,支持自定义时间单位。
●boolean awaitUntil(Date deadline) throws InterruptedException:当前线程进入等待状态直到被通知、中断或者到达超时时间。
针对Object的notify/notifyAll方法:
●void signal():唤醒一个等待在condition上的线程,将该线程从等待队列中转移到同步队列中,如果在同步队列中能够竞争到Lock则可以从等待方法中返回。
●void signalAll():与上一个的区别在于能够唤醒所有等待在condition上的线程。
②代码分析
1、主要流程
JUC中Condition接口的主要实现类是AQS的内部类ConditionObject,它内部维护了一个队列,我们可以称之为等待队列,在某个Condition上等待的线程被signal/signalAll后,ConditionObject会将对应的节点转移到外部类AQS的同步队列中,线程需要获取到AQS同步队列的锁,才可以继续恢复执行后续的用户代码。
await()方法的主要流程为:
●创建节点加入到等待队列。
●释放互斥锁。
●只要没有转移到同步队列就阻塞(等待其他线程调用signal/signalAll或是被中断)。
●重新获取互斥锁。
signal()方法的主要流程为:
●将队列中第一个节点转移到同步队列。
●根据情况决定是否要唤醒对应线程。
●唤醒后从await()方法的循环中退出,开始重新获取互斥锁。
2、对象创建
通过lock.newCondition()方法创建condition对象,而这个方法实际上会new出一个ConditionObject对象。
在锁机制的实现上,AQS内部维护了一个同步队列,如果是独占式锁的话,所有获取锁失败的线程的尾插入到同步队列。同样的,condition内部也是使用相似的方式,内部维护了一个等待队列,所有调用await()方法的线程会加入到等待队列中,并且线程状态转换为等待状态。
通过属性字段可以看出,ConditionObject通过持有等待队列的头尾指针来管理等待队列。主要注意的是Node类复用了在AQS中的Node类,其节点状态和相关属性可以参考AQS中的相关说明。
值得一提的是,与AQS的同步队列不同,ConditionObject持有的等待队列是一个单向队列。
可以多次调用lock.newCondition()方法创建多个condition对象,也就是一个lock可以持有多个等待队列。而在之前利用Object的方式实际上是指在对象Object对象监视器上只能拥有一个同步队列和一个等待队列,而并发包中的Lock拥有一个同步队列和多个等待队列。
3、await()方法
当调用condition.await()方法后会使得当前获取锁的线程进入到等待队列,如果该线程能够从await()方法返回的话一定是该线程获取了与condition相关联的锁。
public final void await() throws InterruptedException {
// 响应中断
if (Thread.interrupted())
throw new InterruptedException();
// 将当前线程包装成Node,尾插入到等待队列中
Node node = addConditionWaiter();
// 释放当前线程所持有的锁,在释放的过程中会唤醒同步队列中的下一个节点(如果没有持有锁,会抛出异常)
int savedState = fullyRelease(node);
int interruptMode = 0;
// 检测此节点的线程是否在同步队列上。如果不在,则说明该线程还不具备竞争锁的资格,继续等待,直到检测到此节点在同步队列上
// 在同步队列的三种可能情况:1、其它线程调用signal()将当前线程节点转移到同步队列并唤醒当前线程;2、其它线程调用signalAll;3、其它线程中断了当前线程,当前线程会自行尝试进入同步队列
while (!isOnSyncQueue(node)) {
LockSupport.park(this);
// 如果已经中断了,则退出
if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
break;
}
// 重新获取互斥锁过程中如果中断并且interruptMode不为"抛出异常",将interruptMode设置为REINTERRUPT
if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
interruptMode = REINTERRUPT;
// 清理等待队列中的不是在等待条件的节点
if (node.nextWaiter != null) // clean up if cancelled
unlinkCancelledWaiters();
// 如果线程发生过中断则根据THROW_IE或是REINTERRUPT分别抛出异常或者重新中断
if (interruptMode != 0)
reportInterruptAfterWait(interruptMode);
}
可以简单整理下代码流程:首先将当前线程新建一个节点同时加入到等待队列中,然后释放当前线程持有的锁。然后则是不断检测该节点代表的线程是否出现在同步队列中,如果不存在则一直挂起,否则参与竞争同步状态。
ⅰ、addConditionWaiter()
private Node addConditionWaiter() {
// 读取lastWaiter
Node t = lastWaiter;
// If lastWaiter is cancelled, clean out.
// 如果等待队列中最后一个waiter节点状态不为CONDITION,则调用unlinkCancelledWaiters()清理队列
if (t != null && t.waitStatus != Node.CONDITION) {
unlinkCancelledWaiters();
// 重读lastWaiter
t = lastWaiter;
}
// 将当前线程包装成Node
Node node = new Node(Thread.currentThread(), Node.CONDITION);
// t如果为null, 初始化firstWaiter为当前节点
if (t == null)
firstWaiter = node;
else
// 将队尾的next连接到node
t.nextWaiter = node;
lastWaiter = node;
return node;
}
方法作用主要是将当前线程加入到等待队列中。在加入到尾节点之前会清除所有状态不为CONDITION的节点。同时可以看出,等待队列是一个不带头节点的链式队列。
ⅱ、fullyRelease()
final int fullyRelease(Node node) {
boolean failed = true;
try {
// 获取节点状态(锁次数)
int savedState = getState();
// 释放锁,失败会抛出异常
if (release(savedState)) {
failed = false;
return savedState;
} else {
throw new IllegalMonitorStateException();
}
} finally {
if (failed)
node.waitStatus = Node.CANCELLED;
}
}
方法作用主要是释放该线程持有的锁。(同时唤醒后续线程)
ⅲ、isOnSyncQueue()
final boolean isOnSyncQueue(Node node) {
// 节点状态为CONDITION一定是在等待队列,或者前项节点为null也说明在等待队列(等待队列里节点是用nextWaiter来维护的,不用next和prev)
if (node.waitStatus == Node.CONDITION || node.prev == null)
return false;
// 如果next不为null,一定是在同步队列的(同步队列在移出时,node会把next指向自己)
if (node.next != null) // If has successor, it must be on queue
return true;
// 有可能node.prev的值不为null,但还没在队列中。(因为入队时CAS队列的tail可能失败)此时从tail向前遍历一次,确定node是否已经在同步队列上
return findNodeFromTail(node);
}
// 从尾部向前遍历,判断节点是否在同步队列中
private boolean findNodeFromTail(Node node) {
Node t = tail;
for (;;) {
if (t == node)
return true;
if (t == null)
return false;
t = t.prev;
}
}
方法作用主要是判断一个节点是否在同步队列上,是则返回true。
ⅳ、checkInterruptWhileWaiting()
private int checkInterruptWhileWaiting(Node node) {
// 线程未中断返回0
// 线程中断且进入同步队列成功,返回THROW_IE,后续会抛出InterruptedException
// 线程中断但未能进入同步队列,则返回REINTERRUPT,后续重新中断
return Thread.interrupted() ?
(transferAfterCancelledWait(node) ? THROW_IE : REINTERRUPT) :
0;
}
// 进入同步队列
final boolean transferAfterCancelledWait(Node node) {
if (compareAndSetWaitStatus(node, Node.CONDITION, 0)) {
enq(node);
return true;
}
// 进入同步队列失败,原因是signal()方法被调用,状态抢先更新了。此时自旋等待节点进入同步队列。
while (!isOnSyncQueue(node))
Thread.yield();
return false;
}
方法作用主要是检查是否阻塞,及阻塞后相关情况返回。
ⅴ、unlinkCancelledWaiters()
private void unlinkCancelledWaiters() {
Node t = firstWaiter;
// 此变量记录上一个非CONDITION节点
Node trail = null;
while (t != null) {
Node next = t.nextWaiter;
if (t.waitStatus != Node.CONDITION) {
// 断开链接
t.nextWaiter = null;
if (trail == null)
// 如果trail为null,取当前节点的后继作为头节点的值(next可以为null)
firstWaiter = next;
else
// 如果trail不为null,把trail连接到当前节点的后继
trail.nextWaiter = next;
// 如果当前节点没有后继了,更新lastWaiter为trail
if (next == null)
lastWaiter = trail;
}
else
// 记录非CONDITION节点
trail = t;
t = next;
}
}
方法作用主要是清除所有状态不为CONDITION的节点。
ⅵ、reportInterruptAfterWait()
private void reportInterruptAfterWait(int interruptMode)
throws InterruptedException {
if (interruptMode == THROW_IE)
throw new InterruptedException();
else if (interruptMode == REINTERRUPT)
selfInterrupt();
}
static void selfInterrupt() {
Thread.currentThread().interrupt();
}
方法作用主要是判断阻塞类型,如果是THROW_IE则抛出InterruptedException,如果是REINTERRUPT则重新中断当前线程。
4、signal()方法
调用condition的signal()或者signalAll()方法可以将等待队列中等待时间最长的节点移动到同步队列中,使得该节点能够有机会获得锁。按照等待队列是先进先出(FIFO)的,所以等待队列的头节点必然会是等待时间最长的节点,也就是每次调用condition的signal()方法是将头节点移动到同步队列中。
public final void signal() {
// 先检测当前线程是否已经获取锁
if (!isHeldExclusively())
throw new IllegalMonitorStateException();
// 获取等待队列中第一个节点
Node first = firstWaiter;
if (first != null)
doSignal(first);
}
ⅰ、doSignal()
private void doSignal(Node first) {
do {
// 移出头节点
if ( (firstWaiter = first.nextWaiter) == null)
lastWaiter = null;
first.nextWaiter = null;
// 如果转移到同步队列失败并且下一个节点不为null,则重试
} while (!transferForSignal(first) && (first = firstWaiter) != null);
}
// 将节点转移到同步队列中
final boolean transferForSignal(Node node) {
// 将该节点从状态CONDITION改变为初始状态0(如果失败,可能是状态已经变为CANCELLED)
if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
return false;
// 将节点加入到同步队列中去,返回的是同步队列中node前面的一个节点
Node p = enq(node);
int ws = p.waitStatus;
// 如果结点p的状态为CANCELLED或者修改waitStatus失败,则直接唤醒线程,以便其能开始尝试竞争锁
if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
LockSupport.unpark(node.thread);
return true;
}
方法主要做了两件事:1、把头节点移出等待队列。2、将移出的头节点放入同步队列。
整体来看,signal()方法流程如下:
●判断当前线程是否已经获取了锁,如果没有获取则直接抛出异常,因为获取锁为唤醒的前置条件。
●如果线程已经获取了锁,则将唤醒等待队列的头节点。
●唤醒头节点时,先将等待队列中的头节点移出,然后调用AQS的enq()方法将其安全地移到同步队列中。
●最后判断如果该节点的同步状态为CANCELLED,或者修改状态为SIGNAL失败时,则直接调用LockSupport唤醒该节点的线程,以便其能开始尝试竞争锁。
5、signalAll()方法
此处大部分内容与signal()方法相似,直接来看区别的地方。
private void doSignalAll(Node first) {
// 将firstWaiter和lastWaiter先置为null
lastWaiter = firstWaiter = null;
// 从first开始一直遍历到第一个null节点,对每个节点都做transferForSignal()操作
do {
Node next = first.nextWaiter;
first.nextWaiter = null;
transferForSignal(first);
first = next;
} while (first != null);
}
可以看到此方法会从头节点开始遍历,从而实现唤醒所有的操作。
6、总结
直接来看一个完整的等待与唤醒的流程:
●await线程先通过lock.lock()方法获取锁成功后调用了await()方法进入等待队列。
●另一个signal线程通过lock.lock()方法获取锁成功后调用了signal()或者signalAll()方法,使得await线程能够有机会移入到同步队列中。
●当其他线程释放锁后,await线程就有机会尝试获取锁,从而使得await线程能够从await()方法中退出,执行后续操作。如果await线程获取锁失败会直接进入到同步队列。

系列文章传送门:
JUC探险-1、初识概貌
JUC探险-2、synchronized
JUC探险-3、volatile
JUC探险-4、final
JUC探险-5、原子类
JUC探险-6、Lock & AQS
JUC探险-7、ReentrantLock
JUC探险-8、ReentrantReadWriteLock
JUC探险-9、Condition
JUC探险-10、常见工具、数据结构
JUC探险-11、ConcurrentHashMap
JUC探险-12、CopyOnWriteArrayList
JUC探险-13、ConcurrentLinkedQueue
JUC探险-14、ConcurrentSkipListMap
JUC探险-15、BlockingQueue
JUC探险-16、ThreadLocal
JUC探险-17、线程池