我需要以原子方式操作两个队列,并且不确定什么是正确的同步策略:这就是我正在尝试的:
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锁?]?提供处理多个阻塞队列的原子性的正确模式是什么?
答案 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()
阻止您有一些选项
Runnable
Executor
中完成工作
moveToFirst()
并使用BlockingQueue.poll(int, TimeUnit)
BlockingQueue.drainTo(secondQ, 1)
并修改moveToFirst()
以返回布尔值以指示其是否成功。对于上述三个选项,您不需要信号量。
最后,我质疑是否需要将移动原子化。如果多个线程正在添加或从队列中删除,则观察队列将无法判断moveToFirst()
是否为原子。