我可以获得一个完整的简单场景,即建议如何使用它的教程,特别是与队列一起使用吗?
和方法旨在wait()提供notify()一种机制以允许线程阻塞直到满足特定条件。为此,我假设您想要编写一个阻塞队列实现,其中您有一些固定大小的元素后备存储。
wait()
notify()
您要做的第一件事是确定您希望方法等待的条件。在这种情况下,您将希望该put()方法阻塞,直到存储中有可用空间,并且您希望该take()方法阻塞,直到有一些元素要返回。
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()或的任何调用notify()都在同步的代码区域内(其中wait()和notify()调用在同一个对象上同步)。造成这种情况的原因(除了标准线程安全问题)是由于被称为丢失信号的东西。
这方面的一个例子是,一个线程可能会put()在队列恰好满时调用,然后它检查条件,看到队列已满,但是在它可以阻塞之前调度另一个线程。然后,第二个线程take()是队列中的一个元素,并通知等待线程队列不再满。然而,因为第一个线程已经检查了条件,所以它会wait()在重新调度后简单地调用,即使它可以取得进展。
通过在共享对象上同步,您可以确保不会发生此问题,因为在第take()一个线程实际阻塞之前,第二个线程的调用将无法进行。
其次,由于被称为虚假唤醒的问题,您需要将正在检查的条件放入 while 循环而不是 if 语句中。这是有时可以重新激活等待线程而不notify()被调用的地方。将此检查放入while循环将确保如果发生虚假唤醒,将重新检查条件,并wait()再次调用线程。
正如其他一些答案所提到的,Java 1.5 引入了一个新的并发库(在java.util.concurrent包中),旨在提供对等待/通知机制的更高级别的抽象。使用这些新功能,您可以像这样重写原始示例:
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(); } } }
当然,如果你真的需要一个阻塞队列,那么你应该使用 BlockingQueue接口的实现。
此外,对于此类内容,我强烈推荐Java Concurrency in Practice,因为它涵盖了您可能想了解的有关并发相关问题和解决方案的所有内容。