简体   繁体   English

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

[英]A simple scenario using wait() and notify() in java

我可以得到一个完整的简单方案,即建议如何使用它的教程,特别是在队列中吗?

The wait() and notify() methods are designed to provide a mechanism to allow a thread to block until a specific condition is met. wait()notify()方法旨在提供一种机制,允许线程阻塞直到满足特定条件为止。 For this I assume you're wanting to write a blocking queue implementation, where you have some fixed size backing-store of elements. 为此,我假设您要编写一个阻塞队列实现,其中具有一些固定大小的元素后备存储。

The first thing you have to do is to identify the conditions that you want the methods to wait for. 您要做的第一件事是确定您希望方法等待的条件。 In this case, you will want the put() method to block until there is free space in the store, and you will want the take() method to block until there is some element to return. 在这种情况下,您将希望在存储中没有可用空间之前阻塞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;
    }
}

There are a few things to note about the way in which you must use the wait and notify mechanisms. 关于必须使用等待和通知机制的方式,需要注意一些事项。

Firstly, you need to ensure that any calls to wait() or notify() are within a synchronized region of code (with the wait() and notify() calls being synchronized on the same object). 首先,您需要确保对wait()notify()任何调用都在代码的同步区域内(其中wait()notify()调用在同一对象上同步)。 The reason for this (other than the standard thread safety concerns) is due to something known as a missed signal. 造成这种情况的原因(除了标准线程安全问题之外)是由于某种原因导致的信号丢失。

An example of this, is that a thread may call put() when the queue happens to be full, it then checks the condition, sees that the queue is full, however before it can block another thread is scheduled. 这样的一个示例是,当队列碰巧已满时,线程可以调用put() ,然后检查条件,查看队列是否已满,但是在可以阻止另一个线程调度之前。 This second thread then take() 's an element from the queue, and notifies the waiting threads that the queue is no longer full. 然后,第二个线程take()是队列中的一个元素,并通知等待线程该队列不再满。 Because the first thread has already checked the condition however, it will simply call wait() after being re-scheduled, even though it could make progress. 因为第一个线程已经检查了条件,所以即使重新安排了进度,它也只会在重新调度之后简单地调用wait()

By synchronizing on a shared object, you can ensure that this problem does not occur, as the second thread's take() call will not be able to make progress until the first thread has actually blocked. 通过在共享库上进行同步,可以确保不会发生此问题,因为第二个线程的take()调用直到第一个线程实际被阻塞后才能取得进展。

Secondly, you need to put the condition you are checking in a while loop, rather than an if statement, due to a problem known as spurious wake-ups. 其次,由于被称为虚假唤醒的问题,您需要将要检查的条件放入while循环中,而不是if语句中。 This is where a waiting thread can sometimes be re-activated without notify() being called. 在这里有时可以在不调用notify()情况下重新激活等待线程。 Putting this check in a while loop will ensure that if a spurious wake-up occurs, the condition will be re-checked, and the thread will call wait() again. 将此检查放在while循环中将确保如果发生虚假唤醒,将重新检查条件,并且线程将再次调用wait()


As some of the other answers have mentioned, Java 1.5 introduced a new concurrency library (in the java.util.concurrent package) which was designed to provide a higher level abstraction over the wait/notify mechanism. 正如其他答案中提到的那样,Java 1.5引入了一个新的并发库(位于java.util.concurrent包中),该库旨在在等待/通知机制上提供更高级别的抽象。 Using these new features, you could rewrite the original example like so: 使用这些新功能,您可以像这样重写原始示例:

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();
        }
    }
}

Of course if you actually need a blocking queue, then you should use an implementation of the BlockingQueue interface. 当然,如果您实际上需要阻塞队列,则应该使用BlockingQueue接口的实现。

Also, for stuff like this I'd highly recommend Java Concurrency in Practice , as it covers everything you could want to know about concurrency related problems and solutions. 另外,对于这种事情,我强烈建议在实践中使用Java Concurrency ,因为它涵盖了您可能想知道的与并发相关的问题和解决方案的所有内容。

Not a queue example, but extremely simple :) 不是队列示例,但是非常简单:)

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();
        }
    }
}

Some important points: 一些要点:
1) NEVER do 1)永远不要

 if(!pizzaArrived){
     wait();
 }

Always use while(condition), because 始终使用while(condition),因为

  • a) threads can sporadically awake from waiting state without being notified by anyone. a)线程可能会零星地从等待状态中唤醒,而不会被任何人通知。 (even when the pizza guy didn't ring the chime, somebody would decide try eating the pizza.). (即使披萨小伙子没有敲响钟声,也会有人决定尝试吃披萨。)
  • b) You should check for the condition again after acquiring the synchronized lock. b)获取同步锁后,应再次检查条件。 Let's say pizza don't last forever. 假设比萨饼不会永远持续下去。 You awake, line-up for the pizza, but it's not enough for everybody. 你醒了,披萨的阵容,但对每个人来说还不够。 If you don't check, you might eat paper! 如果不检查,可能会吃纸! :) (probably better example would be while(!pizzaExists){ wait(); } . :)(可能更好的例子是while(!pizzaExists){ wait(); }

2) You must hold the lock (synchronized) before invoking wait/nofity. 2)在调用等待/无效之前,您必须保持锁(同步)。 Threads also have to acquire lock before waking. 线程也必须在唤醒之前获取锁。

3) Try to avoid acquiring any lock within your synchronized block and strive to not invoke alien methods (methods you don't know for sure what they are doing). 3)尝试避免在同步块内获取任何锁,并努力不调用外来方法(您不确定它们在做什么的方法)。 If you have to, make sure to take measures to avoid deadlocks. 如果需要,请确保采取措施避免死锁。

4) Be careful with notify(). 4)注意notify()。 Stick with notifyAll() until you know what you are doing. 坚持使用notifyAll(),直到您知道自己在做什么。

5)Last, but not least, read Java Concurrency in Practice ! 5)最后但并非最不重要的一点,请阅读实践Java并发

Even though you asked for wait() and notify() specifically, I feel that this quote is still important enough: 即使您具体要求了wait()notify() ,我仍然认为这句话很重要:

Josh Bloch, Effective Java 2nd Edition , Item 69: Prefer concurrency utilities to wait and notify (emphasis his): Josh Bloch, Effective Java 2nd Edition ,项目69:建议并发实用程序waitnotify (强调他的):

Given the difficulty of using wait and notify correctly, you should use the higher-level concurrency utilities instead [...] using wait and notify directly is like programming in "concurrency assembly language", as compared to the higher-level language provided by java.util.concurrent . 考虑到正确使用waitnotify的困难,您应该使用更高级别的并发实用程序,而不是直接使用waitnotify ,就像使用“并发汇编语言”进行编程,相比之下, java.util.concurrent There is seldom, if ever, reason to use wait and notify in new code . 几乎没有理由使用wait并在新代码中进行notify

Have you taken a look at this Java Tutorial ? 您看过此Java教程了吗?

Further, I'd advise you to stay the heck away from playing with this kind of stuff in real software. 此外,我建议您不要玩真正的软件中的这类东西。 It's good to play with it so you know what it is, but concurrency has pitfalls all over the place. 使用它很好,这样您就可以知道它是什么,但是并发性到处都是陷阱。 It's better to use higher level abstractions and synchronized collections or JMS queues if you are building software for other people. 如果要为其他人构建软件,最好使用更高级别的抽象和同步的集合或JMS队列。

That is at least what I do. 至少那是我要做的。 I'm not a concurrency expert so I stay away from handling threads by hand wherever possible. 我不是并发专家,所以我尽量避免手动处理线程。

Example

public class myThread extends Thread{
     @override
     public void run(){
        while(true){
           threadCondWait();// Circle waiting...
           //bla bla bla bla
        }
     }
     public synchronized 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
     }

}

Example for wait() and notifyall() in Threading. Threading中的wait()和notifyall()的示例。

A synchronized static array list is used as resource and wait() method is called if the array list is empty. 同步的静态数组列表用作资源,如果数组列表为空,则调用wait()方法。 notify() method is invoked once a element is added for the array list. 为数组列表添加元素后,将调用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());
   }
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM