我知道这个问题之前已被多次询问和回答,但我无法弄清楚互联网上的示例,例如this或that。
这两个解决方案都会在notifyAll
方法中检查阻塞队列的数组/队列/链表到空put()
等待线程的空虚,反之亦然。get()
方法。第二个链接中的comment强调了这种情况,并提到这不是必要的。
所以问题是;检查队列是否为空,对我来说似乎有点奇怪完全通知所有等待的线程。有什么想法吗?
提前致谢。
答案 0 :(得分:19)
我知道这是一个古老的问题,但在阅读了问题和答案后,我无法帮助自己,我希望你觉得这很有用。
关于在通知其他等待线程之前检查队列是否实际已满或为空,您遗漏的方法put (T t)
和T get()
都是synchronized
方法,这意味着只有一个线程可以一次输入其中一个方法,但这不会阻止它们一起工作,所以如果一个线程-a已经输入put (T t)
方法,另一个线程-b仍然可以进入并开始执行在线程-a退出T get()
之前的put (T t)
方法,因此这个double-checking
设计将使开发人员感觉更安全,因为您无法知道未来的cpu上下文是否切换意志何时会发生。
更好,更推荐的方法是使用Reentrant Locks
和Conditions
:
//我已经编辑了此link
的源代码Condition isFullCondition;
Condition isEmptyCondition;
Lock lock;
public BQueue() {
this(Integer.MAX_VALUE);
}
public BQueue(int limit) {
this.limit = limit;
lock = new ReentrantLock();
isFullCondition = lock.newCondition();
isEmptyCondition = lock.newCondition();
}
public void put (T t) {
lock.lock();
try {
while (isFull()) {
try {
isFullCondition.await();
} catch (InterruptedException ex) {}
}
q.add(t);
isEmptyCondition.signalAll();
} finally {
lock.unlock();
}
}
public T get() {
T t = null;
lock.lock();
try {
while (isEmpty()) {
try {
isEmptyCondition.await();
} catch (InterruptedException ex) {}
}
t = q.poll();
isFullCondition.signalAll();
} finally {
lock.unlock();
}
return t;
}
使用这种方法不需要double checking
,因为lock
对象在两个方法之间共享,这意味着只有一个线程a或b可以一次输入任何这些方法,这与同步方法不同这会创建不同的监视器,并且只有那些因为队列已满而等待的线程才会在有更多空间时被通知,并且由于队列为空,等待线程也是如此,这将导致更好的CPU利用率。
您可以在源代码here
答案 1 :(得分:1)
我认为逻辑上在notifyAll()
之前进行额外检查是没有害处的。
从队列中放入/获取内容后,您可以简单地notifyAll()
。一切仍然有效,你的代码更短。但是,在调用notifyAll()
之前,检查是否有人可能正在等待(通过检查是否命中队列边界)也没有任何危害。这个额外的逻辑可以节省不必要的notifyAll()
次调用。
这取决于您是否需要更简洁,更清晰的代码,或者您希望代码更有效地运行。 (没有查看notifyAll()
的实现。如果没有人等待是一个便宜的操作,那么无论如何额外检查的性能提升可能并不明显。)
答案 2 :(得分:0)
作者使用notifyAll()
的原因很简单:他们不知道是否有必要,所以他们决定选择“更安全”的选项。
在上面的示例中,只要为添加的每个元素调用notify()
就足够了,在所有情况下只能提供一个等待的线程。
这变得更加明显,如果您的队列也可以选择在一个步骤中添加多个元素,如addAll(Collection<T> list)
,因为在这种情况下可以提供等待空列表的多个线程,确切地说:添加了许多线程作为元素。
notifyAll()
会导致特殊单元素情况下的额外开销,因为许多线程被不必要地唤醒,因此必须再次进入休眠状态,同时阻止队列访问。因此,将notifyAll()
替换为notify()
可以提高此特殊情况的速度。
但是不使用wait / notify并且完全同步,但是使用并发包会比任何智能等待/通知实现更多地提高速度。
答案 3 :(得分:0)
我想编写一个简单的阻塞队列实现,这将帮助人们轻松理解这一点。这是给新手的。
class BlockingQueue {
private List queue = new LinkedList();
private int limit = 10;
public BlockingQueue(int limit){
this.limit = limit;
}
public synchronized void enqueue(Object ele) throws InterruptedException {
while(queue.size() == limit)
wait();
if(queue.size() == 0)
notifyAll();
// add
queue.add(ele);
}
public synchronized Object deque() throws InterruptedException {
while (queue.size() == 0)
wait();
if(queue.size() == limit)
notifyAll();
return queue.remove(0);
}
}