概述
condition与Lock的实现类结合使用。 如果Lock替换了synchronized方法和语句的使用,则Condition将替换Object监视方法(wait,notify和notifyAll)的使用。
condition,也称为condition queue或者condition variables,能让一个线程阻塞在条件变量上,直到其他线程通知该线程条件变量现在可能为true。当一个线程等待条件时,它会自动释放相关联的锁,并陷入阻塞状态,就跟Object.wait()方法一样。
一个Condition实例一个Lock实例绑定,要获取特定Lock实例的Condition实例,可以调用Lock实例的newCondition()方法。
以生产者/消费者模式为例,假设我们有一个有界缓冲区,它支持put和take方法。 如果缓冲区为空,则消费线程将阻塞,直到缓冲区有内容为止; 如果缓冲区已满,则生产线程阻塞,直到缓冲区有空间可用为止。 我们希望把put线程和take线程放在不同的waitset中,以便我们在缓冲区中的内容可用或空间可用时,实现只通知一个线程。 这可以使用两个Condition实例来实现。
class BoundedBuffer {
final Lock lock = new ReentrantLock();
final Condition notFull = lock.newCondition();
final Condition notEmpty = lock.newCondition();
final Object[] items = new Object[100];
int putptr, takeptr, count;
public void put(Object x) throws InterruptedException {
lock.lock();
try {
while (count == items.length)
notFull.await();
items[putptr] = x;
if (++putptr == items.length) putptr = 0;
++count;
notEmpty.signal();
} finally {
lock.unlock();
}
}
public Object take() throws InterruptedException {
lock.lock();
try {
while (count == 0)
notEmpty.await();
Object x = items[takeptr];
if (++takeptr == items.length) takeptr = 0;
--count;
notFull.signal();
return x;
} finally {
lock.unlock();
}
}
}
Condition实现类提供与Object监视器方法不同的行为和语义,例如在waitset中阻塞等待时可以不响应中断,多个waitset。
在等待条件时,虚假唤醒是可以发生的。因此应用程序的程序员应该编写循环,在循环中等待条件。
等待条件有以下三种形式:
1、不可中断:void awaitUninterruptibly();
2、可中断:void await() throws InterruptedException;
3、定时: long awaitNanos(long nanosTimeout) throws InterruptedException; 返回值表示当前剩余的时间。
await()方法
await方法使当前线程阻塞等待,直到被通知或者被中断。
与该condition关联的lock会被自动释放,并且由于线程调度的原因线程变得不可用,直到以下情形之一发生:
1、其他线程调用了这个condition的signal()方法,并且当前线程被选为唤醒的线程;
2、其他线程调用了这个condition的signalAll()方法;
3、其他线程调用当前线程的Thread.interrupt()方法;
4、一个虚假唤醒发生时;
以上所有情形中,当前线程都必须重新获得与该condition关联的lock,才能从await方法返回。
如果当前线程在阻塞等待时被中断,将会抛出 InterruptedException,并清除中断状态。
signal()方法
唤醒等待线程。
如果存在线程在条件上等待,选择其中一个作为被唤醒的线程。
在调用该方法时,要求当前线程持有与condition关联的Lock。否则抛出IllegalMonitorStateException。
ConditionObject是Condition在java并发中的具体实现,它是AQS的内部类。因为Condition相关操作都需要获取锁,所以作为AQS的内部类很合理。
该方法实现了响应中断的条件等待。
1、前置检查,如果线程被中断,抛出中断异常。
2、当前线程封装成节点添加到条件队列。
3、唤醒当前线程的节点(头节点)的后继节点。调用await方法的当前线程必须是持有锁的,当前线程所在的节点只能是持有锁的头节点。所以它在使用state作为参数调用release方法唤醒头节点的后继结点时应该总会成功,如果失败抛出IllegalMonitorStateException。
4、调用park方法阻塞等待直到收到signal信号或者中断信号。
5、线程被唤醒后,从while循环退出,调用acquireQueued方法自旋等待获取锁。
6、如果阻塞过程中发生中断,线程被唤醒后会判断中断状态,并抛出中断异常。
ConditionObject的await方法与object.wait方法的流程比较:
wait方法:进入waitSet >> 释放锁 >> 阻塞等待条件变量 >> 被唤醒,进入entryList等待获取锁
await方法:进入条件队列 >> 唤醒同步队列的后继节点 >> 阻塞等待条件变量 >> 被唤醒,自旋等待获取锁
/**
* Implements interruptible condition wait.
*
* - If current thread is interrupted, throw InterruptedException.
*
- Save lock state returned by {@link #getState}.
*
- Invoke {@link #release} with saved state as argument,
* throwing IllegalMonitorStateException if it fails.
*
- Block until signalled or interrupted.
*
- Reacquire by invoking specialized version of
* {@link #acquire} with saved state as argument.
*
- If interrupted while blocked in step 4, throw InterruptedException.
*
*/
public final void await() throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();//前置检查,如果线程被中断,抛出中断异常
Node node = addConditionWaiter(); //当前线程封装成节点添加到条件队列
int savedState = fullyRelease(node); //唤醒当前线程的节点(头节点)的后继节点
int interruptMode = 0;
while (!isOnSyncQueue(node)) { //如果节点不在同步队列中
LockSupport.park(this); //调用park方法阻塞等待
if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
break; //如果是因为中断被唤醒,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、如果条件队列中没有节点,设置firstWaiter的引用;如果条件队列中已有节点,设置lastWaiter的nextWaiter引用;
2、修改lastWaiter的引用;
/**
* Adds a new waiter to wait queue.
* @return its new wait node
*/
private Node addConditionWaiter() {
Node t = lastWaiter;
// If lastWaiter is cancelled, clean out.
if (t != null && t.waitStatus != Node.CONDITION) {
unlinkCancelledWaiters();
t = lastWaiter;
}
Node node = new Node(Thread.currentThread(), Node.CONDITION);//将当前线程封装成节点
if (t == null) //如果尾节点为空,说明条件队列中没有节点
firstWaiter = node; //设置条件队列的头节点的引用
else //如果条件队列中已有节点
t.nextWaiter = node; //设置条件队列的尾节点的nextWaiter引用
lastWaiter = node; //最后修改条件队列的尾节点的引用
return node;
}
从条件队列中解除cencelled节点的链接。
/**
* Unlinks cancelled waiter nodes from condition queue.
* Called only while holding lock. This is called when
* cancellation occurred during condition wait, and upon
* insertion of a new waiter when lastWaiter is seen to have
* been cancelled. This method is needed to avoid garbage
* retention in the absence of signals. So even though it may
* require a full traversal, it comes into play only when
* timeouts or cancellations occur in the absence of
* signals. It traverses all nodes rather than stopping at a
* particular target to unlink all pointers to garbage nodes
* without requiring many re-traversals during cancellation
* storms.
*/
private void unlinkCancelledWaiters() {
Node t = firstWaiter;
Node trail = null;
while (t != null) {
Node next = t.nextWaiter;
if (t.waitStatus != Node.CONDITION) {
t.nextWaiter = null; //删除它的next引用
if (trail == null)
firstWaiter = next; //让firstWaiter指向它原先的next引用
else
trail.nextWaiter = next;//让它的前驱的next引用指向它原先的next引用
if (next == null)
lastWaiter = trail;
}
else
trail = t; //用trail记录循环过程中节点的有效(waitStatus为condition)前驱节点
t = next;
}
}
根据当前状态值调用release方法,返回当前状态值。
/**
* Invokes release with current state value; returns saved state.
* Cancels node and throws exception on failure.
* @param node the condition node for this wait
* @return previous sync state
*/
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;
}
}
唤醒同步队列的头节点的后继节点。
/**
* Releases in exclusive mode. Implemented by unblocking one or
* more threads if {@link #tryRelease} returns true.
* This method can be used to implement method {@link Lock#unlock}.
*
* @param arg the release argument. This value is conveyed to
* {@link #tryRelease} but is otherwise uninterpreted and
* can represent anything you like.
* @return the value returned from {@link #tryRelease}
*/
public final boolean release(int arg) {
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h); //唤醒头节点的后继节点
return true;
}
return false;
}
conditionObject与object的wait/notify方法的一个很大区别就是,实现了在阻塞等待条件变量时,可以不响应中断。
在conditionObject中,判断park方法是从因中断返回,还是因为signal返回有2种方式:
1、调用Thread.interrupted()判断线程的中断状态是否为true。
2、调用isOnSyncQueue(node)判断节点是否在同步队列中,如果在同步队列中,则是因signal返回。
下面会说到,调用conditionObject的signal方法,会将线程所在的节点从条件队列转移到同步队列中。
在该方法实现中,如果是因为中断唤醒,则节点不在同步队列中,仍满足while循环条件,会继续调用park方法阻塞等待。
/**
* Implements uninterruptible condition wait.
*
* - Save lock state returned by {@link #getState}.
*
- Invoke {@link #release} with saved state as argument,
* throwing IllegalMonitorStateException if it fails.
*
- Block until signalled.
*
- Reacquire by invoking specialized version of
* {@link #acquire} with saved state as argument.
*
*/
public final void awaitUninterruptibly() {
Node node = addConditionWaiter();
int savedState = fullyRelease(node);
boolean interrupted = false;
while (!isOnSyncQueue(node)) { //如果节点不在同步队列中
LockSupport.park(this); //调用park方法阻塞等待
if (Thread.interrupted())
interrupted = true;
}
if (acquireQueued(node, savedState) || interrupted)
selfInterrupt();
}
1、前置检查,判断当前线程是否是获取了锁的线程,如果不是抛出IllegalMonitorStateException。
2、获取条件队列的头结点,头结点不为空执行doSignal方法。
/**
* Moves the longest-waiting thread, if one exists, from the
* wait queue for this condition to the wait queue for the
* owning lock.
*
* @throws IllegalMonitorStateException if {@link #isHeldExclusively}
* returns {@code false}
*/
public final void signal() {
if (!isHeldExclusively())
throw new IllegalMonitorStateException();
Node first = firstWaiter;
if (first != null)
doSignal(first);
}
调用transferForSignal方法将条件队列的头节点从条件队列转移到同步队列,并且,将该节点从条件队列删除。
/**
* Removes and transfers nodes until hit non-cancelled one or
* null. Split out from signal in part to encourage compilers
* to inline the case of no waiters.
* @param first (non-null) the first node on condition queue
*/
private void doSignal(Node first) {
do {
if ( (firstWaiter = first.nextWaiter) == null)
lastWaiter = null;
first.nextWaiter = null;
} while (!transferForSignal(first) &&
(first = firstWaiter) != null);
}
将节点从条件队列转移到同步队列,如果转移成功,返回true。如果在signal该节点之前,该节点已经取消,返回false。
1、使用cas将节点的waitStatus从CONDITION设置为0,如果不成功,说明节点的waitStatus已经是cancelled,返回false。
2、将节点放入同步队列,并获取它在同步队列的前驱节点的waitStatus。
3、如果前驱节点的waitStatus不为cancelled,使用cas将该前驱节点的waitStatus设置为SIGNAL。如果前驱节点的waitStatus为cancelled,或者cas操作执行不成功,调用unpark方法唤醒该节点的线程进行resync。
为什么在当前驱节点的waitStatus为cancelled,或者将waitStatus设置为SIGNAL的cas操作执行不成功时,才调用unpark方法唤醒该节点的线程?调用signal方法不总应该是调用unpark方法唤醒线程吗?前驱节点的waitStatus为cancelled,还唤醒后继节点的线程,是否意味着该前驱节点的waitStatus是错误的?
首先,调用ConditionObject的signal方法,不像Object.notify方法那样,最终总会调用unpark方法唤醒线程。它将节点从条件队列转移到同步队列,将调用unpark方法唤醒线程的操作交给了节点的前驱节点去做。当前驱节点成为头节点后,释放锁时会唤醒该节点。当然,有个前提条件是,节点的前驱节点的waitStatus必须为SIGNAL,才会在释放锁时唤醒后继节点。所以,只有当前驱节点的waitStatus为cancelled,或者将其waitStatus设置为SIGNAL的cas操作执行不成功时,才会在这里就调用unpark方法唤醒线程。
/**
* Transfers a node from a condition queue onto sync queue.
* Returns true if successful.
* @param node the node
* @return true if successfully transferred (else the node was
* cancelled before signal)
*/
final boolean transferForSignal(Node node) {
/*
* If cannot change waitStatus, the node has been cancelled.
*/
if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
return false;
/*
* Splice onto queue and try to set waitStatus of predecessor to
* indicate that thread is (probably) waiting. If cancelled or
* attempt to set waitStatus fails, wake up to resync (in which
* case the waitStatus can be transiently and harmlessly wrong).
*/
Node p = enq(node); //将节点放入同步队列,并返回它的前驱节点
int ws = p.waitStatus;
if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
LockSupport.unpark(node.thread);
return true;
}
参考:condition API