相关类图:
使用Condition和 object作为条件队列的区别:
object: 只能绑定一个内部队列,使用notify()唤醒是随机唤醒这个队列中的一个线程,而且这个线程并不是我们想要唤醒的那个线程,我们无法控制,而且那个线程可能还不一定满足唤醒的判断条件;
使用notifyAll()唤醒的是这个队列所有的线程,之后这些线程会再次判断是否符合唤醒的判断条件,此时达到了我们希望唤醒的线程成功唤醒的目的。
也是大多数时候,选用notifyAll()而不是notify()的原因。但是,notifyAll能够更容易确保类的行为的正确性,但是有时候可能会比较低效。因为将出现大量的上下文切换操作以及锁获取的竞争操作。
总而言之,object绑定的内部队列上的线程由于各自等待的条件可能有所不同,因此在调用notify/notifyAll 时进行处理的时候,显得不是那么容易处理。
Condition:可以绑定多个Condition对象到一个Lock对象实例,若在一个队列上的线程的各自等待条件也是各不相同,那么在调用signal/signalAll 的情形和object调用notify/notifyAll 的情形是一致的。但是,如果是那样去使用Condition就失去了其本身的意义,此时应当充分利用可绑定多个队列到一个Lock对象上的特性,根据等待的判断条件去决定创建多少的condition对象,从而确保在每一个队列上的线程的各自等待条件都是一样的。然后,通过 signal 就能唤醒某一个相同判断条件的队列上的一个线程,通过signalAll 可以唤醒该队列上的所有线程。从而不仅很容易确保类的行为的正确性,而且还避免出现大量的上下文切换和锁竞争操作。
Condition 具体实例:
假设使用object wait() 来控制三个线程执行顺序:线程A-->线程C-->线程B;现在A和C都处于挂起等待状态, 在执行线程B时,如果想确保唤醒的一定是A线程,此时使用object的notify 是无法满足要求的,但是Condition signal可以办到。
创建两个Condition条件队列:
ReentrantLock lock = new ReentrantLock(true);
Condition aCondition = reentrantLock.newCondition();
Condition cCondition = reentrantLock.newCondition();
线程A:
{
lock.lock();while(A 挂起条件)
aCondition.await();//此时当前线程释放lock锁,进入等待状态,等待其他线程执行aCondition.signal()时才有可能执行
A do something
lock.unlock();
}
线程C:
{
lock.lock();while(C 挂起条件)
cCondition.await();//此时当前线程释放lock锁,进入等待状态,等待其他线程执行cCondition.signal()时才有可能执行
do something
lock.unlock();
}
线程B:
{
lock.lock();aCondition.signal();//此时当前线程释放lock锁,随机唤醒一个处于等待状态等待aCondition的线程,继续执行await后面的程序。
lock.unlock();
}
线程B通过aCondition.signal(); 就能确保是唤醒的是等待aCondition的某一个线程,而不是等待cCondition的线程。
完整代码如下:
package com.thread;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class LockTest {
Lock lock = new ReentrantLock();
Condition aCondition = lock.newCondition();
Condition cCondition = lock.newCondition();
public void aTest() {//a线程
lock.tryLock();
try {
System.out.println("----aTest挂起----");
aCondition.await();
System.out.println("----aTest被唤醒----");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
System.out.println("----aTest执行完毕----");
}
public void cTest() {//c线程
lock.tryLock();
try {
System.out.println("----cTest挂起----");
cCondition.await();
System.out.println("----cTest被唤醒----");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
System.out.println("----cTest执行完毕----");
}
public void bTest() {//main线程作为b线程
try {
lock.tryLock();
System.out.println("bTest唤醒aTest");
aCondition.signal();//唤醒同样需要持有相同的lock对象锁
} finally {
lock.unlock();//不释放锁,则a线程始终无法继续执行。
}
try {
System.out.println("释放锁,让aTest继续执行");
Thread.sleep(1000);//让a线程竞争到锁
lock.tryLock();//a线程执行完毕,此时将继续向下执行唤醒c线程操作
System.out.println("bTest唤醒cTest");
cCondition.signal();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public static void main(String[] args) throws InterruptedException {
LockTest lockTest = new LockTest();
new ALockThread(lockTest).start();
new CLockThread(lockTest).start();
Thread.sleep(1000);//确保a和c线程都已启动
lockTest.bTest();
}
}
package com.thread;
public class ALockThread extends Thread{
private LockTest lockTest;
ALockThread(LockTest lockTest){
this.lockTest=lockTest;
}
@Override
public void run() {
lockTest.aTest();
}
}
package com.thread;
public class CLockThread extends Thread{
private LockTest lockTest;
CLockThread(LockTest lockTest){
this.lockTest=lockTest;
}
@Override
public void run() {
lockTest.cTest();
}
}
运行结果为:
----aTest挂起----
----cTest挂起----
bTest唤醒aTest
释放锁,让aTest继续执行
----aTest被唤醒----
----aTest执行完毕----
bTest唤醒cTest
----cTest被唤醒----
----cTest执行完毕----
需要注意的是,此处的signal() 换成 signalAll 的运行结果不变,不变的原因上面已经进行了说明。
java.util.concurrent.locks.Condition 源码:
package java.util.concurrent.locks;
import java.util.concurrent.TimeUnit;
import java.util.Date;
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();
}
接口 Condition
所有已知实现类:
AbstractQueuedLongSynchronizer.ConditionObject,
AbstractQueuedSynchronizer.ConditionObject
Condition
通过与任意 Lock
实现组合使用,为每个对象提供多个wait-set。其中,Lock
替代了 synchronized
方法和语句的使用,Condition
替代了 Object 监视器方法(wait
、notify
和 notifyAll
)的使用。
条件(也称为_条件队列_ 或_条件变量_)为线程提供了一个含义,以便在某个状态条件现在可能为 true 的另一个线程通知它之前,一直挂起该线程(即让其“等待”)。因为访问此共享状态信息发生在不同的线程中,所以它必须受保护,因此要将某种形式的锁与该条件相关联。等待提供一个条件的主要属性是:以原子方式 释放相关的锁,并挂起当前线程,就像 Object.wait
做的那样。
Condition
实例实质上被绑定到一个锁上。要为特定 Lock
实例获得 Condition
实例,请使用 newCondition()
方法。
示例:
假定有一个绑定的缓冲区,它支持 put
和 take
方法。如果试图在空的缓冲区上执行 take
操作,则在某一个项变得可用之前,线程将一直阻塞;如果试图在满的缓冲区上执行 put
操作,则在有空间变得可用之前,线程将一直阻塞。我们喜欢在单独的等待 set 中保存 put
线程和 take
线程,这样就可以在缓冲区中的项或空间变得可用时利用最佳规划,一次只通知一个线程。可以使用两个 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
监视器方法的行为和语义,比如受保证的通知排序,或者在执行通知时不需要保持一个锁。如果某个实现提供了这样特殊的语义,则该实现必须记录这些语义。
注意,Condition
实例只是一些普通的对象,它们自身可以用作 synchronized
语句中的目标,并且可以调用自己的 wait
和 notification
监视器方法。为了避免混淆,建议除了在其自身的实现中之外,切勿在 synchronized
语句使用 Condition
实例作为锁对象。
void
**await**()
造成当前线程在接到信号或被中断之前一直处于等待状态。
boolean
**[await](http://tool.oschina.net/uploads/apidocs/jdk-zh/java/util/concurrent/locks/Condition.html#await(long,%20java.util.concurrent.TimeUnit))**(long time, [TimeUnit](http://tool.oschina.net/uploads/apidocs/jdk-zh/java/util/concurrent/TimeUnit.html) unit)
造成当前线程在接到信号、被中断或到达指定等待时间之前一直处于等待状态。
long
**awaitNanos**(long nanosTimeout)
造成当前线程在接到信号、被中断或到达指定等待时间之前一直处于等待状态。
void
**awaitUninterruptibly**()
造成当前线程在接到信号之前一直处于等待状态。
boolean
**awaitUntil**([Date](http://tool.oschina.net/uploads/apidocs/jdk-zh/java/util/Date.html) deadline)
造成当前线程在接到信号、被中断或到达指定最后期限之前一直处于等待状态。
void
**[signal](http://tool.oschina.net/uploads/apidocs/jdk-zh/java/util/concurrent/locks/Condition.html#signal())**()
唤醒一个等待线程。
void
**[signalAll](http://tool.oschina.net/uploads/apidocs/jdk-zh/java/util/concurrent/locks/Condition.html#signalAll())**()
唤醒所有等待线程。
await
void await() throws InterruptedException
造成当前线程在接到信号或被 中断之前一直处于等待状态。
与此 Condition
相关的锁将以原子方式释放,并且出于线程调度的目的,将禁用当前线程,且在发生以下情况_之一_ 以前,当前线程将一直处于休眠状态:
- 其他某个线程调用此
Condition
的signal()
方法,并且碰巧将当前线程选为被唤醒的线程; - 其他某个线程调用此
Condition
的signalAll()
方法; - 其他某个线程中断当前线程,且当前线程支持中断线程的挂起;则抛出
InterruptedException
,并清除当前线程的中断状态。
在所有情况下,当前线程唤醒重新执行,都必须重新获取与此条件有关的锁。
抛出:
[InterruptedException](http://tool.oschina.net/uploads/apidocs/jdk-zh/java/lang/InterruptedException.html)
- 如果当前线程被中断(并且支持中断线程挂起)
awaitUninterruptibly
void awaitUninterruptibly()
造成当前线程在接到信号之前一直处于等待状态。
与此条件相关的锁以将原子方式释放,并且出于线程调度的目的,将禁用当前线程,且在发生以下情况_之一_ 以前,当前线程将一直处于休眠状态:
- 其他某个线程调用此
Condition
的signal()
方法,并且碰巧将当前线程选为被唤醒的线程; - 其他某个线程调用此
Condition
的signalAll()
方法;
在所有情况下,当前线程唤醒重新执行,都必须重新获取与此条件有关的锁。
如果在进入此方法时设置了当前线程的中断状态,或者在等待时,线程被中断,那么在接到信号之前,它将继续等待。当最终从此方法返回时,仍然将其设置为中断状态。
awaitNanos
long awaitNanos(long nanosTimeout) throws InterruptedException
造成当前线程在接到信号、被中断或到达指定等待时间之前一直处于等待状态。
与此条件相关的锁以原子方式释放,并且出于线程调度的目的,将禁用当前线程,且在发生以下情况_之一_ 以前,当前线程将一直处于休眠状态:
- 其他某个线程调用此
Condition
的signal()
方法,并且碰巧将当前线程选为被唤醒的线程; - 其他某个线程调用此
Condition
的signalAll()
方法; - 其他某个线程中断当前线程,且支持中断线程的挂起;则抛出
InterruptedException
,并且清除当前线程的已中断状态。 - 已超过指定的等待时间;
在所有情况下,当前线程唤醒重新执行,都必须重新获取与此条件有关的锁。
在返回时,该方法返回了所剩毫微秒数的一个估计值,以等待所提供的 nanosTimeout
值的时间,如果超时,则返回一个小于等于 0 的值。可以用此值来确定在等待返回但某一等待条件仍不具备的情况下,是否要再次等待,以及再次等待的时间。此方法的典型用法采用以下形式:
synchronized boolean aMethod(long timeout, TimeUnit unit) { long nanosTimeout = unit.toNanos(timeout); while (!conditionBeingWaitedFor) { if (nanosTimeout > 0) nanosTimeout = theCondition.awaitNanos(nanosTimeout); else return false; } // ... }
设计注意事项:此方法需要一个 nanosecond 参数,以避免在报告剩余时间时出现截断错误。在发生重新等待时,这种精度损失使得程序员难以确保总的等待时间不少于指定等待时间。
参数:
nanosTimeout
- 等待的最长时间,以毫微秒为单位
返回:
nanosTimeout
值减去花费在等待此方法的返回结果的时间的估算。正值可以用作对此方法进行后续调用的参数,来完成等待所需时间结束。小于等于零的值表示没有剩余时间。
抛出:
[InterruptedException](http://tool.oschina.net/uploads/apidocs/jdk-zh/java/lang/InterruptedException.html)
- 如果当前线程被中断(并且支持中断线程挂起)
await
boolean await(long time, TimeUnit unit) throws InterruptedException
造成当前线程在接到信号、被中断或到达指定等待时间之前一直处于等待状态。此方法在行为上等效于:
awaitNanos(unit.toNanos(time)) > 0
参数:
time
- 最长等待时间
unit
- time
参数的时间单位
返回:
如果在从此方法返回前检测到等待时间超时,则返回 false
,否则返回 true
抛出:
[InterruptedException](http://tool.oschina.net/uploads/apidocs/jdk-zh/java/lang/InterruptedException.html)
- 如果当前线程被中断(并且支持中断线程挂起)
awaitUntil
boolean awaitUntil(Date deadline) throws InterruptedException
造成当前线程在接到信号、被中断或到达指定最后期限之前一直处于等待状态。
与此条件相关的锁以原子方式释放,并且出于线程调度的目的,将禁用当前线程,且在发生以下情况_之一_ 以前,当前线程将一直处于休眠状态:
- 其他某个线程调用此
Condition
的signal()
方法,并且碰巧将当前线程选为被唤醒的线程; - 其他某个线程调用此
Condition
的signalAll()
方法; - 其他某个线程中断当前线程,且支持中断线程的挂起;则抛出
InterruptedException
,并且清除当前线程的已中断状态。 - 指定的最后期限到了;
在所有情况下,当前线程唤醒重新执行,都必须重新获取与此条件有关的锁。
返回值指示是否到达最后期限,使用方式如下:
synchronized boolean aMethod(Date deadline) { boolean stillWaiting = true; while (!conditionBeingWaitedFor) { if (stillWaiting) stillWaiting = theCondition.awaitUntil(deadline); else return false; } // ... }
参数:
deadline
- 一直处于等待状态的绝对时间
返回:
如果在返回时已经到达最后期限,则返回 false
,否则返回 true
抛出:
[InterruptedException](http://tool.oschina.net/uploads/apidocs/jdk-zh/java/lang/InterruptedException.html)
- 如果当前线程被中断(并且支持中断线程挂起)
signal
void signal()
唤醒一个等待线程。
如果所有的线程都在等待此条件,则选择其中的一个唤醒。在从 await
返回之前,该线程必须重新获取锁。
signalAll
void signalAll()
唤醒所有等待线程。
如果所有的线程都在等待此条件,则唤醒所有线程。在从 await
返回之前,每个线程都必须重新获取锁。