在java中使用wait()和notify()的简单场景

我可以得到一个完整的简单的scheme,即教程,build议如何使用,特别是与队列?

wait()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和notify机制。

首先,你需要确保任何wait()notify()调用都在代码的同步区域内( wait()notify()调用在同一个对象上同步)。 这个原因(除了标准的线程安全问题之外)是由于一些被称为错过的信号。

一个例子就是一个线程可能会在队列发生满时调用put() ,然后检查条件,发现队列已满,但是在它阻塞另一个线程之前就已经调度了。 然后,第二个线程take()从队列中take()一个元素,并通知等待的线程该队列不再满。 因为第一个线程已经检查了这个条件,所以在重新调度之后,它会简单地调用wait() ,尽pipe它可以取得进展。

通过在共享对象上进行同步,可以确保不会发生此问题,因为第二个线程的take()调用将无法进展,直到第一个线程实际上被阻塞。

其次,由于被称为虚假唤醒的问题,你需要把你正在检查的条件放在一个while循环中,而不是if语句。 这是一个等待线程有时可以被重新激活而不notify()被调用的地方。 在while循环中放置这个检查将确保如果发生虚假的唤醒,条件将被重新检查,并且线程将再次调用wait()


正如其他一些答案所提到的那样,Java 1.5引入了一个新的并发库(在java.util.concurrent包中),它被devise为通过wait / notify机制提供更高层次的抽象。 使用这些新function,您可以像这样重写原始示例:

 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实践中的并发 ,因为它涵盖了所有你可能想知道的关于并发相关的问题和解决scheme。

不是一个队列的例子,但非常简单:)

 class MyHouse { private boolean pizzaArrived = false; public void eatPizza(){ synchronized(this){ while(!pizzaArrived){ wait(); } } System.out.println("yumyum.."); } public void pizzaGuy(){ synchronized(this){ this.pizzaArrived = true; notifyAll(); } } } 

一些重要的观点:
1)从来没有

  if(!pizzaArrived){ wait(); } 

总是使用while(条件),因为

  • a)线程可以偶尔从等待状态中醒来而不被任何人通知。 (即使披萨家伙没有响铃,有人会决定尝试吃比萨饼)。
  • b)在获得同步locking后,应再次检查条件。 比方说,比萨不会永远持续下去。 你醒来,为比萨饼排队,但这是不够的每个人。 如果你不检查,你可能会吃纸! :)(可能更好的例子是while(!pizzaExists){ wait(); }

2)在调用wait / nofity之前,您必须保持locking(synchronized)。 线程还必须在醒来之前获得locking。

3)尽量避免在同步块中获取任何锁,并努力不要调用外来方法(你不知道自己在做什么的方法)。 如果必须的话,一定要采取措施避免死锁。

4)注意通知()。 坚持与notifyAll(),直到你知道你在做什么。

5)最后但并非最不重要的是,阅读Java Concurrency in Practice !

即使你特别要求wait()notify() ,我觉得这个引用仍然足够重要:

Josh Bloch, Effective Java第2版 ,第69项:倾向于使用并发实用程序来waitnotify (强调他):

鉴于使用waitnotify正确的困难,您应该使用更高级别的并发实用程序,而不是使用waitnotify直接就像在“并发汇编语言”编程,相比,由更高级别的语言提供java.util.concurrent很less,如果有的话,理由使用wait并在新的代码notify

你有没有看过这个Java教程 ?

此外,我build议你不要在真正的软件中玩这种东西。 玩这个游戏很好,所以你知道它是什么,但是并发在各地都有陷阱。 如果您正在为其他人构build软件,最好使用更高级别的抽象和同步集合或JMS队列。

这至less是我所做的。 我不是一个并发专家,所以我尽可能远离手动处理线程。

 public class myThread extends Thread{ @override public void run(){ while(true){ threadCondWait();// Circle waiting... //bla bla } } public syncronized void threadCondWait(){ while(myCondition){ wait();//Comminucate with notify() } } } public class myAnotherThread extends Thread{ @override public void run(){ //Bla Bla bla notify();//Trigger wait() Next Step } } 

线程中的wait()和notifyall()示例。

如果数组列表为空,则使用同步的静态数组列表作为资源,如果数组列表为空,则调用wait()方法。 一旦为数组列表添加元素,就会调用notify()方法。

 public class PrinterResource extends Thread{ //resource public static List<String> arrayList = new ArrayList<String>(); public void addElement(String a){ //System.out.println("Add element method "+this.getName()); synchronized (arrayList) { arrayList.add(a); arrayList.notifyAll(); } } public void removeElement(){ //System.out.println("Remove element method "+this.getName()); synchronized (arrayList) { if(arrayList.size() == 0){ try { arrayList.wait(); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } }else{ arrayList.remove(0); } } } public void run(){ System.out.println("Thread name -- "+this.getName()); if(!this.getName().equalsIgnoreCase("p4")){ this.removeElement(); } this.addElement("threads"); } public static void main(String[] args) { PrinterResource p1 = new PrinterResource(); p1.setName("p1"); p1.start(); PrinterResource p2 = new PrinterResource(); p2.setName("p2"); p2.start(); PrinterResource p3 = new PrinterResource(); p3.setName("p3"); p3.start(); PrinterResource p4 = new PrinterResource(); p4.setName("p4"); p4.start(); try{ p1.join(); p2.join(); p3.join(); p4.join(); }catch(InterruptedException e){ e.printStackTrace(); } System.out.println("Final size of arraylist "+arrayList.size()); } }