0%

Java并发-条件变量、wait()与notify()、await()与signal()

Object.wait()为什么要配合while使用?

wait之前一般都是要判断某个条件成立才会wait,这个判断条件需要写成while:

1
2
3
while (check pass) {
wait();
}

如果写成if,wait过后当前线程交出锁,等当前线程重新被唤起后,条件是否满足是不知道的,其他线程可能修改了条件的状态,要重新判断一下,否则可能条件未满足就继续执行了。

以生产者消费者模型为例。

  • 有1个生产者往缓冲区加数据,有2个消费者从缓冲区取数据。
  • 消费者取数据前会检查缓冲区是否为空,不为空才能取数据,为空的话要等待。
  • 假设两个消费者线程都等待了。
  • 然后生产者往缓冲区添加数据后做notifyAll唤醒所有消费者,会唤醒两个等待的消费者线程,让消费者线程从监视器的等待队列移动到锁竞争队列,两者竞争锁。
  • 其中一个线程竞争到锁后,消费了缓冲区数据,缓冲区没有数据了,释放锁后另外一个消费者获得到了锁,开始取从缓冲区取数据,而没有再做条件检查了。

这种现象叫做虚假唤醒。

Object的notify()和notifyAll()有什么区别?

每一个对象都有一个内部锁,即监视器(Monitor),虚拟机会给每个对象维护两个线程集合(可能是队列),一个叫Entry Set(入口集),另外一个叫Wait Set(等待集),对于任意对象object,object的Entry Set用于存储等待获取object内部锁的所有线程,object的Wait Set存储执行了object.wait()和object.wait(long timeout)的线程。

  • notify()会唤醒Wait Set里的一个线程。
  • notifyAll()会唤醒Wait Set里的所有线程,线程被唤醒后去竞争获取锁,没有获取锁的线程进入Entry Set。

什么时候用notify()?什么时候用notifyAll()?

如果所有线程都在等待相同的条件,并且一次只有一个线程可以从条件变为true,则可以使用notify。

在这种情况下,notify是优于notifyAll 因为唤醒所有这些因为我们知道只有一个线程会受益而所有其他线程将再次等待,所以调用notifyAll方法只是浪费CPU。

虽然这看起来很合理,但仍有一个警告,即无意中的接收者吞下了关键通知。通过使用notifyAll,我们确保所有收件人都会收到通知。

Object的wait()/notifyAll()和Condition的await()/signalAll()的区别?

以生产者消费者模型为例。

  • 使用wait()、notify()/notifyAll()的缺点在于在生产者唤醒消费者、或者消费者唤醒生产者时,由于生产者和消费者使用同一个锁,所以生产者也会将生产者唤醒,消费者也会将消费者唤醒。
  • 如果能让消费者只唤醒生产者,或者生产者只唤醒消费者,就没有性能浪费了。
  • 所以一个ReentrantLock支持创建多个Condition,以应对这种场景。
  • synchronized的锁对象没有提供多条件唤醒。

ArrayBlockingQueue是典型的生产者消费者的例子,源码是很好的参考。