The wait()
and notify()
方法旨在提供一种机制,允许线程阻塞直到满足特定条件。为此,我假设您想要编写一个阻塞队列实现,其中有一些固定大小的元素后备存储。
您要做的第一件事是确定您希望方法等待的条件。在这种情况下,您将需要put()
方法进行阻塞,直到存储中有可用空间,并且您将需要take()
方法阻塞,直到有一些元素要返回。
public class BlockingQueue<T> {
private Queue<T> queue = new LinkedList<T>();
private int capacity;
public BlockingQueue(int capacity) {
this.capacity = capacity;
}
public synchronized void put(T element) throws InterruptedException {
while(queue.size() == capacity) {
wait();
}
queue.add(element);
notify(); // notifyAll() for multiple producer/consumer threads
}
public synchronized T take() throws InterruptedException {
while(queue.isEmpty()) {
wait();
}
T item = queue.remove();
notify(); // notifyAll() for multiple producer/consumer threads
return item;
}
}
关于使用等待和通知机制的方式,有几点需要注意。
首先,您需要确保任何调用wait()
or notify()
位于代码的同步区域内(带有wait()
and notify()
在同一对象上同步调用)。造成这种情况的原因(除了标准线程安全问题之外)是由于丢失信号造成的。
一个例子是,线程可以调用put()
当队列碰巧已满时,它会检查条件,发现队列已满,但在它可以阻止另一个线程被调度之前。然后第二个线程take()
是队列中的一个元素,并通知等待线程队列不再满。然而,因为第一个线程已经检查了条件,所以它只会调用wait()
重新安排后,尽管可以取得进展。
通过在共享对象上同步,您可以确保不会发生此问题,因为第二个线程的take()
在第一个线程实际阻塞之前,调用将无法取得进展。
其次,由于存在虚假唤醒问题,您需要将要检查的条件放入 while 循环中,而不是 if 语句中。这是有时可以重新激活等待线程而无需notify()
被召唤。将此检查放在 while 循环中将确保如果发生虚假唤醒,将重新检查条件,并且线程将调用wait()
again.
正如其他一些答案所提到的,Java 1.5 引入了一个新的并发库(在java.util.concurrent
包),旨在为等待/通知机制提供更高级别的抽象。使用这些新功能,您可以像这样重写原始示例:
public class BlockingQueue<T> {
private Queue<T> queue = new LinkedList<T>();
private int capacity;
private Lock lock = new ReentrantLock();
private Condition notFull = lock.newCondition();
private Condition notEmpty = lock.newCondition();
public BlockingQueue(int capacity) {
this.capacity = capacity;
}
public void put(T element) throws InterruptedException {
lock.lock();
try {
while(queue.size() == capacity) {
notFull.await();
}
queue.add(element);
notEmpty.signal();
} finally {
lock.unlock();
}
}
public T take() throws InterruptedException {
lock.lock();
try {
while(queue.isEmpty()) {
notEmpty.await();
}
T item = queue.remove();
notFull.signal();
return item;
} finally {
lock.unlock();
}
}
}
当然,如果您确实需要一个阻塞队列,那么您应该使用阻塞队列 http://java.sun.com/javase/6/docs/api/java/util/concurrent/BlockingQueue.html界面。
另外,对于这样的东西我强烈推荐Java 并发实践 http://www.amazon.co.uk/Java-Concurrency-Practice-Brian-Goetz/dp/0321349601,因为它涵盖了您可能想了解的有关并发相关问题和解决方案的所有内容。