两个BlockingQueue - 死锁

时间:2009-09-30 06:41:51

标签: java deadlock

我需要以原子方式操作两个队列,并且不确定什么是正确的同步策略:这就是我正在尝试的:

public class transfer {

    BlockingQueue firstQ;
    BlockingQueue secondQ;

    public moveToSecond() {
        synchronized (this){
            Object a = firstQ.take();
            secondQ.put(a)
        }
    }

    public moveToFirst() {
        synchronized(this) {
            Object a = secondQ.take();
            firstQ.put(a);
        }
    }
}

这是正确的模式吗?在方法moveToSecond()中,如果firstQ为空,则该方法将在firstQ.take()上等待,但它仍然保持对该对象的锁定。这将阻止moveToFirst()有机会执行。

我对等待期间的锁释放感到困惑 - 线程是否释放所有锁[这个和BlockedQUeue锁?]?提供处理多个阻塞队列的原子性的正确模式是什么?

4 个答案:

答案 0 :(得分:2)

您正在使用正常的方法使用通用互斥锁在两个队列之间进行同步。但是,为了避免您描述第一个队列为空的情况,我建议重新实现moveToFirst()moveToSecond()以使用poll()而不是take(); e.g。

public void boolean moveToFirst() {
  // Synchronize on simple mutex; could use a Lock here but probably
  // not worth the extra dev. effort.
  synchronzied(queueLock) {
    boolean success;

    // Will return immediately, returning null if the queue is empty.
    Object o = firstQ.poll();

    if (o != null) {
      // Put could block if the queue is full.  If you're using a bounded
      // queue you could use add(Object) instead to avoid any blocking but
      // you would need to handle the exception somehow.
      secondQ.put(o);
      success = true;
    } else {
      success = false;
    }
  }

  return success;
}

答案 1 :(得分:1)

你没有提到的另一个失败情况是,如果firstQ不是空的但是secondQ已满,则该项目将从firstQ中移除,但是没有地方可以放置它。

所以唯一正确的方法是使用poll和offer with timeouts和code将事情恢复到任何失败之前的状态(重要!),然后在随机时间之后重试,直到poll和offer都成功为止。

这是一种乐观的做法;在正常操作中有效但在死锁频繁时非常低效(平均延迟取决于所选择的超时)

答案 2 :(得分:0)

您应该使用java.util.concurrency中的Lock-mechanism,如下所示:

Lock lock = new ReentrantLock();
....
lock.lock();
try {
    secondQ.put(firstQ.take());
} finally {
    lock.unlock();
}

对firstQ.put(secondQ.take())执行相同操作,使用相同的锁定对象。

除非您正在编写新的并发原语,否则不再需要在Object类上使用lowlevel wait / notify方法。

答案 3 :(得分:0)

在您的代码中,当线程在BlockingQueue.take()上被阻止时,它会保持this上的锁定。在代码离开同步块或调用this.wait()之前,锁定不会被释放。

我假设moveToFirst()moveToSecond()应该阻止,并且您的类控制对队列的所有访问权。

private final BlockingQueue<Object> firstQ = new LinkedBlockingQueue();
private final Semaphore firstSignal = new Semaphore(0);
private final BlockingQueue<Object> secondQ = LinkedBlockingQueue();
private final Semaphore secondSignal = new Semaphore(0);

private final Object monitor = new Object();

public void moveToSecond() {
  int moved = 0;
  while (moved == 0) {

    // bock until someone adds to the queue
    firstSignal.aquire();

    // attempt to move an item from one queue to another atomically
    synchronized (monitor) {
      moved = firstQ.drainTo(secondQ, 1);
    }
  }
}

public void putInFirst(Object object) {
  firstQ.put(object);

  // notify any blocking threads that the queue has an item
  firstSignal.release();
}

moveToFirst()putInSecond()的代码类似。仅当某些其他代码可能从队列中删除项目时,才需要while。如果您希望队列中删除的方法等待挂起的移动,它应该从信号量中获取许可,并且信号量应该被创建为公平的信号量,因此第一个调用aquire的线程将被释放第一:

firstSignal = new Semaphore(0, true);

如果您不希望moveToFirst()阻止您有一些选项

  1. 让方法在发送到Runnable
  2. Executor中完成工作
  3. 将超时时间传递至moveToFirst()并使用BlockingQueue.poll(int, TimeUnit)
  4. 使用BlockingQueue.drainTo(secondQ, 1)并修改moveToFirst()以返回布尔值以指示其是否成功。
  5. 对于上述三个选项,您不需要信号量。

    最后,我质疑是否需要将移动原子化。如果多个线程正在添加或从队列中删除,则观察队列将无法判断moveToFirst()是否为原子。