引言

Object.wait和notify方法利用在对象上的锁实现了等待通知机制,它们必须与synchronized关键字配合使用。Condition接口提供了与wait/notify类似的等待通知机制,它需要配合Lock来使用。除了提供类似的功能,在某些方面上,Condition与wait/notify还有些不同。这篇文章,我们来看看Condition的用法。

方法分类

Condition接口提供了以下几个方法:

  1. void await() throws InterruptedException;
  2. void awaitUninterruptibly();
  3. long awaitNanos(long nanosTimeout) throws InterruptedException;
  4. boolean await(long time, TimeUnit unit) throws InterruptedException;
  5. boolean awaitUntil(Date deadline) throws InterruptedException;
  6. void signal();
  7. void signalAll();

其中,await**方法用来将当前线程挂起处于等待状态,可以从几个维度对await类的方法进行分类:
按照是否能响应中断awaitUninterruptibly()方法自己一类,其他几个方法一类,可以看到其他几个方法都抛出了InterruptedException,意味着它们会以抛出异常的形式响应线程中断。
按照线程挂起是否有时间限制,分为两类,await()和awaitUninterruptibly()没有时间限制,如果没有其他导致线程唤醒(例如其他线程执行signal或者signalAll方法或者线程中断)会一直处于等待状态,而awaitNanos、await和awaitUntil这三个方法的挂起时间都有时间限制。
signal和signalAll两个方法就可以对比Object的notify和notifyAll方法来想象,就是用来唤醒一个或者所有被挂起的线程。
看完了大致的分类,我们来详细看一下这些方法的使用和相关特性。

方法的使用与注意事项

await和sign类方法前提必须使用lock获取锁

看下面的例子:

  1. public class ConditionTest {
  2. public static void main(String[] args) {
  3. Lock lock = new ReentrantLock();
  4. Condition condition = lock.newCondition();
  5. try {
  6. condition.await();
  7. } catch (InterruptedException e) {
  8. e.printStackTrace();
  9. }
  10. }
  11. }

执行会抛出异常:

  1. Exception in thread "main" java.lang.IllegalMonitorStateException
  2. at java.util.concurrent.locks.ReentrantLock$Sync.tryRelease(ReentrantLock.java:151)
  3. at java.util.concurrent.locks.AbstractQueuedSynchronizer.release(AbstractQueuedSynchronizer.java:1261)
  4. at java.util.concurrent.locks.AbstractQueuedSynchronizer.fullyRelease(AbstractQueuedSynchronizer.java:1723)
  5. at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(AbstractQueuedSynchronizer.java:2036)
  6. at person.andy.concurrency.lock.condition.ConditionTest.main(ConditionTest.java:12)

这个异常我们在wait/notify里面也遇到过,意思是没有获取锁。在使用Condition的await类和sign类方法时,都必须先用lock提前获取锁。我改一下这个例子:

  1. public class ConditionTest {
  2. public static void main(String[] args) {
  3. Lock lock = new ReentrantLock();
  4. Condition condition = lock.newCondition();
  5. lock.lock();
  6. try {
  7. condition.await();
  8. } catch (InterruptedException e) {
  9. e.printStackTrace();
  10. }
  11. lock.unlock();
  12. }
  13. }

就能正常运行了。

调用await()方法后会立即释放锁

  1. public class ConditionTest {
  2. public static void main(String[] args) {
  3. Lock lock = new ReentrantLock();
  4. Condition condition = lock.newCondition();
  5. Thread thread = new Thread(new Runnable() {
  6. @Override
  7. public void run() {
  8. lock.lock();
  9. try {
  10. System.out.println("await线程获得了锁");
  11. condition.await();
  12. Thread.sleep(10000);
  13. } catch (InterruptedException e) {
  14. e.printStackTrace();
  15. }
  16. lock.unlock();
  17. }
  18. },"await_thread");
  19. thread.start();
  20. try {
  21. Thread.sleep(1000);
  22. } catch (InterruptedException e) {
  23. e.printStackTrace();
  24. }
  25. lock.lock();
  26. System.out.println("main线程获得了锁");
  27. lock.unlock();
  28. }
  29. }

在这个例子中,thread线程执行await方法之后,main方法马上就会获取到锁,然后输出“main线程获得了锁”,这个与Object.wait方法是一样的逻辑,都是执行完成后线程立刻处于等待状态,同时锁被释放。

调用sign方法后需要等到lock.unlock才会释放锁

  1. public class ConditionTest {
  2. public static void main(String[] args) throws InterruptedException {
  3. Lock lock = new ReentrantLock();
  4. Condition condition = lock.newCondition();
  5. Thread thread = new Thread(new Runnable() {
  6. @Override
  7. public void run() {
  8. lock.lock();
  9. try {
  10. System.out.println("await线程获得了锁");
  11. condition.await();
  12. System.out.println("await开始睡眠");
  13. Thread.sleep(10000);
  14. } catch (InterruptedException e) {
  15. e.printStackTrace();
  16. }
  17. lock.unlock();
  18. }
  19. },"await_thread");
  20. thread.start();
  21. try {
  22. Thread.sleep(1000);
  23. } catch (InterruptedException e) {
  24. e.printStackTrace();
  25. }
  26. lock.lock();
  27. System.out.println("main线程获得了锁");
  28. condition.signalAll();
  29. Thread.sleep(10000);
  30. lock.unlock();
  31. }
  32. }

这个例子中,main线程调用了signalAll方法后睡眠10秒钟,在这个期间,锁不会被释放,thread线程也不会被唤醒,而是等main线程执行完lock.unlock方法之后,thread线程才会被唤醒。

使用await和sign实现生产者消费者模型

下面是一个完整的用await和sign实现生产者消费者模型的实例:

  1. public class AwaitSign {
  2. private static int count = 0;
  3. private static Lock lock = new ReentrantLock();
  4. private static Condition condition = lock.newCondition();
  5. public static void main(String[] args) {
  6. ExecutorService producers = new ThreadPoolExecutor(5,5,1, TimeUnit.SECONDS,new LinkedBlockingDeque<>());
  7. ExecutorService consumers = new ThreadPoolExecutor(3,3,1,TimeUnit.SECONDS,new LinkedBlockingDeque<>());
  8. for(int i=0;i<1000;i++){
  9. producers.submit(new AwaitSign.Producer());
  10. consumers.submit(new AwaitSign.Consumer());
  11. }
  12. }
  13. static class Consumer implements Runnable{
  14. @Override
  15. public void run() {
  16. try {
  17. Thread.sleep(1000);
  18. } catch (InterruptedException e) {
  19. e.printStackTrace();
  20. }
  21. lock.lock();
  22. while(count == 0){
  23. try {
  24. condition.await();
  25. } catch (InterruptedException e) {
  26. e.printStackTrace();
  27. }
  28. }
  29. count --;
  30. System.out.println("消费者"+Thread.currentThread()+"消费之后count="+count);
  31. condition.signalAll();
  32. lock.unlock();
  33. }
  34. }
  35. static class Producer implements Runnable{
  36. @Override
  37. public void run() {
  38. try {
  39. Thread.sleep(1000);
  40. } catch (InterruptedException e) {
  41. e.printStackTrace();
  42. }
  43. lock.lock();
  44. while(count == 100){
  45. try {
  46. condition.await();
  47. } catch (InterruptedException e) {
  48. e.printStackTrace();
  49. }
  50. }
  51. count ++;
  52. System.out.println("生产者"+Thread.currentThread()+"生产之后count="+count);
  53. condition.signalAll();
  54. lock.unlock();
  55. }
  56. }
  57. }

原理与wait/notify类似,这里不做过多介绍了。

小结

Condition与Lock结合使用的方式类比wait/notify与synchronized关键字的使用,它们都能实现等待通知机制。