条件#信号急切地是最佳做法吗?

时间:2015-08-04 08:58:36

标签: java multithreading concurrency java.util.concurrent

Condition JavaDoc包含以下代码示例:

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();
     }
   }
 }

如果我实现了BoundedBuffer#take,我只会在notFull.signal()时调用count == (items.length - 2),以避免发信号通知其他线程,除非必要。我还注意到ArrayBlockingQueue#removeAt热切地打电话给notFull.signal();

问题:我的支票会引入错误吗?当条件成立时,是否最好实践Java并发编程以急切地发出信号?我认为它会降低死锁的风险。它有任何性能影响吗?

2 个答案:

答案 0 :(得分:1)

是。您的检查引入了一个错误。用一个病态的例子来展示它很容易。

让我们假设count = items.length

Thread1: put(o1); // count == items.length. Waiting on notFull.
Thread2: put(o2); // Waiting on notFull.
Thread3: put(o3); // Waiting on notFull.
Thread4: take();  // count -> items.length - 1
                  // There's space in the buffer, but we never signalled notFull.
                  // Thread1, Thread2, Thread3 will still be waiting to put.
         take();  // count -> items.length - 2, signal notFull!
                  // But what happens if Thread4 manages to keep the lock?
         take();  // count -> items.length - 3
         ...
         take();  // count -> 0
Thread1: // Wakes up from Thread4's signal.
         put(o1); // count -> 1
Thread2: put(o2); // Never signalled, still waiting on notFull.
Thread3: put(o3); // Never signalled, still waiting on notFull.

可以使用signalAll来缓解此问题,但您仍会遇到一些问题:

  1. 由于您即使只打开一个空格也会唤醒每个线程,因此会有更多的锁定争用。
  2. 当缓冲区中只有一个空格打开时,您仍然会有等待put的线程。根据实现/文档,我猜这可以没问题,但在大多数情况下仍然会令人惊讶。

答案 1 :(得分:0)

我的支票会引入错误吗?

是。 条件takeptr ==(items.length - 1)与填充底层缓冲区无关。似乎缓冲区是在"循环"中实现的。方式。

在示例代码中,每次成功获取都会使缓冲区准备就绪,因此您应该每次都发出信号。如果没有,线程将等待" put",尽管缓冲区中有可用空间......

最好的做法是在条件为真时急切地发出Java并发编程吗?

据我记忆,在每个可能使条件变为真的事件之后,有一个通用的经验法则。但是正确的条件检查通常是通过等待线程来完成的。

您的示例场景非常简单(代码调用" signal"确保" notFull"条件将由等待该" notFull"条件),但通常也使用signalAll而不是信号,因为信号方法只发出一个线程(可能实际上不满足条件),这可能会导致死锁。