从上一篇文章我们知道了,Lock接口代替了synchronize用于并发编程的加锁解锁,那么Object monitor的wait、notify功能谁能代替呢,就是我们这篇文章介绍的Condition。
Object的wait、notify是用来做资源状态判断的,比如生产者-消费者模式,消费者获取到了锁,还需要有资源可以消费才能继续执行。当资源不足时,当前线程可以休眠,不用浪费cpu,当生产者生产后,再通知等待资源的消费者去消费。
代码结构
顶级接口
public interface Condition {
void await() throws InterruptedException;
void signal();
void signalAll();
}
Condition类作为条件控制的顶级接口,JUC为我们提供好的类都实现了此接口。
用法和Object类似,await对应Object的wait方法,signal、signalAll对应Object的notify、notifyAll
public interface Lock {
void lock();
void unlock();
void lockInterruptibly() throws InterruptedException;
boolean tryLock();
boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
Condition newCondition();
}
Lock作为加锁解锁的顶级接口,配合Condition使用,而且必须要获取锁后才能调用Condition提供的方法。
实现
AbstractQueuedSynchronizer类中的内部类ConditionObject实现了Condition接口
public abstract class AbstractQueuedSynchronizer{
public class ConditionObject implements Condition, java.io.Serializable {
/** 条件队列的首节点 */
private transient Node firstWaiter;
/** 条件队列的尾节点 */
private transient Node lastWaiter;
使用
以ReentrantLock为例,直接调用ReentrantLock的newCondition就能获得Condition的实例,内部其实的调用的AbstractQueuedSynchronizer的newCondition方法。
public class ReentrantLock implements Lock, java.io.Serializable {
public Condition newCondition() {
return sync.newCondition();
}
abstract static class Sync extends AbstractQueuedSynchronizer {
final ConditionObject newCondition() {
return new ConditionObject();
}
}
代码流程
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();
}
}
}
上面是Doug Lea大神在Condition接口给的例子,逻辑很简单,有一个长度为100的Object数组,put方法是将Object的实例加入到数组中,take方法是从数组中获取Object实例,模拟生产者消费者。我们拿这个类来说一下大体流程
例子说明
比如现在有两组线程,p1-p3调用put()方法生产数据
t1-t3调用take()方法消费数据
lock
首先不管put和take都需要调用lock获取锁才能执行后面的逻辑。此时根据上次讲的lock的流程,假设t1线程获取到了锁,以t2、p1、p2、t3、p3顺序加入了等待队列。那么此时等待队列的内存结构为:
t1获取到锁继续执行
take
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();
}
}
此时数组中还没有数据,t1调用await方法
await
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);
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);
}
addConditionWaiter将线程加入到条件队列中,fullyRelease释放当前线程占用的锁,因为线程已经加入到条件队列需要休眠,所以将之前获取的锁释放掉,这样其他线程才能继续获取锁执行。
while (!isOnSyncQueue(node))判断节点是不是在等待队列中,如果不在则挂起线程。
addConditionWaiter
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;
lastWaiter = node;
return node;
}
构建Node节点,此时状态为Node.CONDITION并加入到条件队列中,如果lastWaiter为空,代表队列为空,设置线程为首节点,否则加入到条件队列队尾。
例子说明
通过上面逻辑,此时将t1线程将入等待队列,此时条件队列数据结构为
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;
}
}
完全释放锁,逻辑可参考上一篇讲的,此时释放锁后,在等待队列中的线程可以继续获取锁。此时等待队列中的t2线程获取锁,继续执行,和t1的逻辑一致,由于数组中还没有对象,继续await。
此时的条件队列的数据结构为
当t2释放锁后,p1线程获取锁继续执行,调用put方法
isOnSyncQueue
final boolean isOnSyncQueue(Node node) {
if (node.waitStatus == Node.CONDITION || node.prev == null)
return false;
if (node.next != null) // If has successor, it must be on queue
return true;
/*
* node.prev can be non-null, but not yet on queue because
* the CAS to place it on queue can fail. So we have to
* traverse from tail to make sure it actually made it. It
* will always be near the tail in calls to this method, and
* unless the CAS failed (which is unlikely), it will be
* there, so we hardly ever traverse much.
*/
return findNodeFromTail(node);
}
判断线程是否在同步队列中,这里我们不考虑中断情况,所以只走第一个if条件判断,判断节点的waitStatus=Node.CONDITION,则说明线程不在同步队列,在条件队列中,返回false。
put
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();
}
}
此时数组的长度为0,不进while循环,将Object实例放入数组中,调用notEmpty.signal
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;
} while (!transferForSignal(first) &&
(first = firstWaiter) != null);
}
将条件队列中的头结点移除并且加入到等待队列中。if条件的逻辑为/如果条件队列只有一个元素的话,则将firstWaiter、lastWaiter置为null,清除对象引用。否则将条件队列头结点向后移动。while循环方法为将节点加入到等待队列中去。
Node节点
static final class Node {
/** 标明当前节点线程取消排队 */
static final int CANCELLED = 1;
/** 标明该节点的后置节点需要自己去唤醒 */
static final int SIGNAL = -1;
/** 标明当前节点在等待某个条件,此时节点在条件队列中 */
static final int CONDITION = -2;
/**
* waitStatus value to indicate the next acquireShared should
* unconditionally propagate
*/
static final int PROPAGATE = -3;
/**
* 等待状态,值对于上面的四个常量
*/
volatile int waitStatus;
/**
* 等待队列节点指向的前置节点
*/
volatile Node prev;
/**
* 等待队列节点指向的后置节点
*/
volatile Node next;
/**
* 当前节点持有的线程
*/
volatile Thread thread;
/**
* 条件队列中节点指向的后置节点
*/
Node nextWaiter;
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;
}
先将Node节点的对象结构贴出来好观察。首先将节点的waitStatus置为0初始状态。调用enq方法将节点加入到等待队列中去(具体看上一篇的分析),并且返回的是该节点在等待队列中的前置节点(代码中的Node p),然后将前置节点的状态设置为Node.SIGNAL,这个状态是指如果节点的waitStatus为Node.SIGNAL,当节点执行完后需要唤醒你的后置节点。此时等待队列和条件队列的内存结构分别为:
到此一个线程获取锁—>进入条件队列—>被唤醒—>进入等待队列整个流程结束。
如有不实,还望指正