是什么
任意一个Java对象,都拥有一组监视器方法(Object类上)
- wait()
- wait(long timeout)
- notify()
- notifyAll()
这些方法与synchronized同步关键字配合,可以实现等待/通知模式。Condition接口也提供了类似Object的监视器方法,与Lock配合可以实现等待/通知模式。
使用例子
获取一个Condition必须通过Lock的newCondition()方法。只有AQS有Condition的实现类,Lock有获取Condition的方法。并且Condition需要与Lock联合使用
当A线程调用await()方法后,A线程会释放锁并在此等待。
其他线程调用Condition对象的signal()方法,通知A线程后才从await()方法返回,并且在返回前已经获取了锁。
Lock lock = new ReentrantLock();
Condition condition = lock.newCondition();
public void conditionWait() throws InterruptedException {
lock.lock();
try {
condition.await();
dosomething();
} finally {
lock.unlock();
}
}
public void conditionSignal() throws InterruptedException {
lock.lock();
try {
condition.signal();
} finally {
lock.unlock();
}
}
接口结构
方法名 | 作用 |
---|---|
void await() | 使当前线程等待,直到收到信号或被中断interrupted,关联的锁被原子释放,并且当前线程出于线程调度目的而被禁用,并且处于休眠状态直到:1.其他线程调用signal方法,并且当前线程恰好被选择为要唤醒的线程;2.其他一些线程调用signalAll;3.某些其他线程interrupts当前线程;4.发生虚假唤醒。在此方法可以返回之前,当前线程必须重新获取与此条件关联的锁 |
void awaitUninterruptibly(); | 相比await(),线程不可被中断 |
long awaitNanos(long nanosTimeout) | 使当前线程等待,直到发出信号或中断它,或者经过指定的等待时间, 相对await()多了超时唤醒,返回值为剩余时间 |
boolean await(long time, TimeUnit unit) | 使当前线程等待,直到发出信号或中断它,或者经过指定的等待时间 |
boolean awaitUntil(Date deadline) | 当前线程进入等待状态,直到被通知或中断,或者经过指定的时间,被通知返回true,到指定时间返回false |
void signal(); | 唤醒一个等待线程。 该线程必须重新获取锁,然后才能从await返回。 |
void signalAll(); | 唤醒所有等待的线程。 每个线程必须重新获取锁,然后才能从await返回。 |
两个名词
- 同步队列
存放在竞争锁时暂时没获取到锁的线程的队列,等待前面的节点释放锁后唤醒
- 等待队列
存放 调用Condition.await方法,线程进入等待状态,需要等其他线程调用Condition.signal的线程
实现类ConditionObject
属性
Condition拥有首节点(firstWaiter)和尾节点(lastWaiter),有头节点就能构建一个等待队列FIFO。
- private transient Node firstWaiter;
等待队列的第一个节点。
- private transient Node lastWaiter;
等待队列的最后一个节点。
void 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);
}
几个注意点:
- 为什么没看到CAS操作:因为发生的一切都是在获取到锁的情况下发生的,依靠锁实现同步状态
- 线程是在什么时候被移动在同步队列的:当其他线程调用signal()方法,并且等待队列中被选中的是该线程
void signal()
- 先将等待队列中的头节点移出,修改相应引用
- 将节点移动到同步队列中
- 判断同步队列的原尾节点状态为Cancel,或者修改状态为Signal失败时,唤醒该线程投入锁的竞争。
- 否则等待同步队列前驱节点释放锁后唤醒线程。
public final void signal() {
//检测线程是否拥有锁
if (!isHeldExclusively())
throw new IllegalMonitorStateException();
// 头节点
Node first = firstWaiter;
if (first != null)
//唤醒头节点
doSignal(first);
}
//唤醒线程以及一些引用修改
private void doSignal(Node first) {
do {
//将头指针后移
if ( (firstWaiter = first.nextWaiter) == null)
lastWaiter = null;
//节点被移出队列,不需要后继引用
first.nextWaiter = null;
} while (!transferForSignal(first) &&
(first = firstWaiter) != null);
}
//将节点从条件队列转移到同步队列。如果成功,则返回true
final boolean transferForSignal(Node node) {
//将该节点从状态CONDITION改变为初始状态0
if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
return false;
//将节点加入到同步队列中去,返回同步队列原尾节点
Node p = enq(node);
int ws = p.waitStatus;
//检查前驱节点的状态,若状态为cancel,尝试唤醒节点
//前驱节点不为取消转态,尝试CAS状态为SIGNAL,如果失败,尝试唤醒节点投入到锁的竞争中去
if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
LockSupport.unpark(node.thread);
return true;
}
线程在调用signal后就会将等待队列的头节点移动到同步队列。等待同步队列的前驱节点唤醒线程。
signalAll就算将所有节点都移动到同步队列中。
总结
- 一个线程获取锁后,通过调用Condition的await()方法,会将当前线程先加入到等待队列中,然后释放锁。
- 将线程挂起,等待其他线程调用signal()将线程移动到同步队列中
- 当线程被唤醒后,尝试竞争锁,竞争到锁后会从await方法进行返回。