Condition实现了Object中的监视器方法(wait和notify)。 举个列子:假定我们有一个缓冲区提供了put和get方法。如果一个线程尝试在一个空缓冲区中使用take方法,这个线程就会被阻塞,直到有一个可以获取的元素;如果一个线程尝试在一个满的缓冲区中使用put方法,它就会被阻塞,直到这个缓冲区有空位。我们因该保持执行put和set方法的线程在不同的等待池中,这样我们的操作才会最佳化。
使用:
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();
}
}
}
上述程序执行流程:
首先创建了一个ReentrantLock的对象Lock,然后又通过Lock创建了两个condition对象notFull和notEmpty(两个condition队列),也就是说这两个condition对象所属同一个Lock之下(即公用一个等待队列),然后还有一个数组代表着资源。
1、假设有两组线程,取和放要同时修改数组,取和取、放和放、取和放之间都会有竞争
2、线程获取资源(尝试获锁)失败,被放到共用的等待队列之中
3、获取资源成功,会进行一个判断:
a、若取线程获取资源成功,则判断数组是否为空,为空则将这个线程放到notEmpty(condition)队列之中并阻塞它,然后释放锁;不为空则消费资源,将一个在notFull队列中等待时间最长的放线程取出,插入到等待队列中,然后释放锁,并唤醒一个在等待队列中等待时间最长的线程
b、若放线程获取资源成功,则判断数组是否已满,满了则将这个线程放到notFull队列之中并阻塞它,然后释放锁;不为空则消费资源,将一个在notEmpty队列中等待时间最长的取线程取出,插入到等待队列中,然后释放锁,并唤醒一个在等待队列中等待时间最长的线程
4、两组线程相互竞争,重复2、3步骤
Condition接口的方法:
public interface Condition {
void await() throws InterruptedException;
void awaitUninterruptibly();
long awaitNanos(long nanosTimeout) throws InterruptedException;
boolean await(long time, TimeUnit unit) throws InterruptedException;
boolean awaitUntil(Date deadline) throws InterruptedException;
void signal();
void signalAll();
}
AQS中有一个Condition接口的实现类ConditionObject,它作为AQS的内部类可以帮我们实现Condition的大部分方法,但要真正的完成它的功能还得和AQS的一些实现类来共同配合
AQS有这样一些属性与Condition相关联:
static final class Node {
static final Node SHARED = new Node();
static final Node EXCLUSIVE = null;
static final int CANCELLED = 1;
static final int SIGNAL = -1;
static final int CONDITION = -2;
static final int PROPAGATE = -3;
volatile int waitStatus;
volatile Thread thread;
Node nextWaiter;
Node() {
}
Node(Thread thread, int waitStatus) {
this.waitStatus = waitStatus;
this.thread = thread;
}
}
Node节点,每个线程都会被包装为节点,在Condition队列中SHARED会失效,因为它只支持独享模式;它是一个有三个指针域的节点,但是它的prev和next指针域应该失效,因为Condition队列中的节点使用nextWaiter来代表后继,next是用在等待队列中的指向下一节点的指针
执行流程:
1、构造
final Lock lock = new ReentrantLock();
上面的例子,首先构造了一个ReentrantLock对象
final Condition notFull = lock.newCondition();
然后调用了ReentrantLock的newCondition方法
public Condition newCondition() {
return sync.newCondition();
}
newCondition方法又会返回AQS中的newCondition方法
final ConditionObject newCondition() {
return new ConditionObject();
}
newCondition返回的是一个ConditionObject对象,ConditionObject是AQS中的一个内部类
2、await
lock.lock();
首先对代码块加上了ReentrantLock锁,这是获得锁的线程就会往下执行,其余线程会被加入等待队列中,至于会不会被阻塞那就要看acquireQueued方法有没有将其阻塞了
notFull.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)
unlinkCancelledWaiters();
if (interruptMode != 0)
reportInterruptAfterWait(interruptMode);
}
1、若当前线程被中断则抛出new InterruptedException
2、否则将当前节点加进Condition队列然后释放锁,调用await方法后并不用显示调用unlock方法,它会自动给我们调用release方法去释放锁
3、若当前节点处于Condition队列中则调用底层park方法去阻塞它直到被唤醒或中断
4、若节点不在Condition队列中则它一定在等待队列中,调用acquireQueued方法在等待队列中将其阻塞
5、清理调被取消的节点
6、若在阻塞时被中断则报异常
以下是相关调用代码:
private Node addConditionWaiter() {
Node t = lastWaiter;
/*若队列不为空并且队列中对后一个节点被取消掉了,做一次清理*/
if (t != null && t.waitStatus != Node.CONDITION) {
unlinkCancelledWaiters();
t = lastWaiter;
}
/*将当前线程包装成节点,并标记成condition状态*/
Node node = new Node(Thread.currentThread(), Node.CONDITION);
/*尾插并返回当前节点*/
if (t == null)
firstWaiter = node;
else
t.nextWaiter = node;
lastWaiter = node;
return node;
}
将新节点加入到等待队列中并返回新的节点
1、若队列不为空并且队列中对最一个节点被取消掉了,做一次清理
2、将当前线程包装成节点,并标记成condition状态
3、尾插并返回当前节点
private void unlinkCancelledWaiters()
这个方法简单来说就是重构队列,舍弃取消掉的节点
final int fullyRelease(Node node) {
boolean failed = true;
try {
/*获取state值*/
int savedState = getState();
/*调用AQS中的release方法,然后release会调用ReentrantLock中重写的tryRelease方法尝试释放锁*/
if (release(savedState)) {
failed = false;
return savedState;
} else {
throw new IllegalMonitorStateException();
}
} finally {
if (failed)
node.waitStatus = Node.CANCELLED;
}
}
1、获取state值
2、调用AQS中的release方法,然后release会调用ReentrantLock中重写的tryRelease方法尝试释放锁(在这里嵌套的重入锁将会被完全释放)
3、若成功,返回savedState,失败则抛出异常
final boolean isOnSyncQueue(Node node) {
if (node.waitStatus == Node.CONDITION || node.prev == null)
return false;
if (node.next != null)
return true;
return findNodeFromTail(node);
}
判断一个节点,一般是一个初始化在condition队列中的节点,现在是否正在尝试重新获取进入sync(等待)队列中的资格, 若是返回true
1、当前节点在之前addConditionWaiter方法中即被加入Condition队列时已经被标记为CONDITION状态,此方法中再次判断它是否为CONDITION状态,或当它是队列中头节点时,返回false,即证明它不再等待队列中
2、若next域有值,则证明它肯定在等待队列中
3、否则返回findNodeFromTail方法的结果
3、notify
notEmpty.signal();
上述代码用来唤醒线程
public final void signal() {
if (!isHeldExclusively())
throw new IllegalMonitorStateException();
Node first = firstWaiter;
if (first != null)
doSignal(first);
}
相比之下signal方法简单了很多,虽然它会是用来唤醒线程的,但它并不会真正的调用unpark方法去唤醒被阻塞的线程,它只是帮我们做一次标记
这个方法会将等待时间最长的节点从Condition队列中移到等待队列中
1、若当前线程不处于独享模式,抛出异常
2、若Condition队列中有节点则唤醒第一个
以下是相关调用代码:
private void doSignal(Node first) {
do {
if ( (firstWaiter = first.nextWaiter) == null)
lastWaiter = null;
first.nextWaiter = null;
} while (!transferForSignal(first) &&
(first = firstWaiter) != null);
}
1、若Condition队列为空,退出循环
2、若头节点被成功放入等待队列则退出循环
3、否则重复1和2
final boolean transferForSignal(Node node) {
/*将节点的waitStatus变为0*/
if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
return false;
/*返回当前节点在等待队列中的前任*/
Node p = enq(node);
int ws = p.waitStatus;
/*前任被阻塞或标记位标记失败*/
if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
LockSupport.unpark(node.thread);
return true;
}
用来改变节点的waitStatus
1、 若一个节点的state状态改变失败则证明其被取消了返回false
2、waitStatus改变成功,就将当前节点尾插到等待队列中
3、若前任被取消或者将前任的waitStatus标记位-1(即标记当前节点应被阻塞)失败则唤醒前任
所以上面说这里并不会唤醒Condition队列中的节点,只是将其插入到等待队列,然后将其waitStatus从-2变为0。真正的唤醒操作还是得
由unlock方法去调用
相类似的方法:
private void doSignalAll(Node first) {
lastWaiter = firstWaiter = null;
do {
Node next = first.nextWaiter;
first.nextWaiter = null;
transferForSignal(first);
first = next;
} while (first != null);
}