引言
Object.wait和notify方法利用在对象上的锁实现了等待通知机制,它们必须与synchronized关键字配合使用。Condition接口提供了与wait/notify类似的等待通知机制,它需要配合Lock来使用。除了提供类似的功能,在某些方面上,Condition与wait/notify还有些不同。这篇文章,我们来看看Condition的用法。
方法分类
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();
其中,await**方法用来将当前线程挂起处于等待状态,可以从几个维度对await类的方法进行分类:
按照是否能响应中断awaitUninterruptibly()方法自己一类,其他几个方法一类,可以看到其他几个方法都抛出了InterruptedException,意味着它们会以抛出异常的形式响应线程中断。
按照线程挂起是否有时间限制,分为两类,await()和awaitUninterruptibly()没有时间限制,如果没有其他导致线程唤醒(例如其他线程执行signal或者signalAll方法或者线程中断)会一直处于等待状态,而awaitNanos、await和awaitUntil这三个方法的挂起时间都有时间限制。
signal和signalAll两个方法就可以对比Object的notify和notifyAll方法来想象,就是用来唤醒一个或者所有被挂起的线程。
看完了大致的分类,我们来详细看一下这些方法的使用和相关特性。
方法的使用与注意事项
await和sign类方法前提必须使用lock获取锁
看下面的例子:
public class ConditionTest {
public static void main(String[] args) {
Lock lock = new ReentrantLock();
Condition condition = lock.newCondition();
try {
condition.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
执行会抛出异常:
Exception in thread "main" java.lang.IllegalMonitorStateException
at java.util.concurrent.locks.ReentrantLock$Sync.tryRelease(ReentrantLock.java:151)
at java.util.concurrent.locks.AbstractQueuedSynchronizer.release(AbstractQueuedSynchronizer.java:1261)
at java.util.concurrent.locks.AbstractQueuedSynchronizer.fullyRelease(AbstractQueuedSynchronizer.java:1723)
at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(AbstractQueuedSynchronizer.java:2036)
at person.andy.concurrency.lock.condition.ConditionTest.main(ConditionTest.java:12)
这个异常我们在wait/notify里面也遇到过,意思是没有获取锁。在使用Condition的await类和sign类方法时,都必须先用lock提前获取锁。我改一下这个例子:
public class ConditionTest {
public static void main(String[] args) {
Lock lock = new ReentrantLock();
Condition condition = lock.newCondition();
lock.lock();
try {
condition.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
lock.unlock();
}
}
调用await()方法后会立即释放锁
public class ConditionTest {
public static void main(String[] args) {
Lock lock = new ReentrantLock();
Condition condition = lock.newCondition();
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
lock.lock();
try {
System.out.println("await线程获得了锁");
condition.await();
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
lock.unlock();
}
},"await_thread");
thread.start();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
lock.lock();
System.out.println("main线程获得了锁");
lock.unlock();
}
}
在这个例子中,thread线程执行await方法之后,main方法马上就会获取到锁,然后输出“main线程获得了锁”,这个与Object.wait方法是一样的逻辑,都是执行完成后线程立刻处于等待状态,同时锁被释放。
调用sign方法后需要等到lock.unlock才会释放锁
public class ConditionTest {
public static void main(String[] args) throws InterruptedException {
Lock lock = new ReentrantLock();
Condition condition = lock.newCondition();
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
lock.lock();
try {
System.out.println("await线程获得了锁");
condition.await();
System.out.println("await开始睡眠");
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
lock.unlock();
}
},"await_thread");
thread.start();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
lock.lock();
System.out.println("main线程获得了锁");
condition.signalAll();
Thread.sleep(10000);
lock.unlock();
}
}
这个例子中,main线程调用了signalAll方法后睡眠10秒钟,在这个期间,锁不会被释放,thread线程也不会被唤醒,而是等main线程执行完lock.unlock方法之后,thread线程才会被唤醒。
使用await和sign实现生产者消费者模型
下面是一个完整的用await和sign实现生产者消费者模型的实例:
public class AwaitSign {
private static int count = 0;
private static Lock lock = new ReentrantLock();
private static Condition condition = lock.newCondition();
public static void main(String[] args) {
ExecutorService producers = new ThreadPoolExecutor(5,5,1, TimeUnit.SECONDS,new LinkedBlockingDeque<>());
ExecutorService consumers = new ThreadPoolExecutor(3,3,1,TimeUnit.SECONDS,new LinkedBlockingDeque<>());
for(int i=0;i<1000;i++){
producers.submit(new AwaitSign.Producer());
consumers.submit(new AwaitSign.Consumer());
}
}
static class Consumer implements Runnable{
@Override
public void run() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
lock.lock();
while(count == 0){
try {
condition.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
count --;
System.out.println("消费者"+Thread.currentThread()+"消费之后count="+count);
condition.signalAll();
lock.unlock();
}
}
static class Producer implements Runnable{
@Override
public void run() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
lock.lock();
while(count == 100){
try {
condition.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
count ++;
System.out.println("生产者"+Thread.currentThread()+"生产之后count="+count);
condition.signalAll();
lock.unlock();
}
}
}
小结
Condition与Lock结合使用的方式类比wait/notify与synchronized关键字的使用,它们都能实现等待通知机制。