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

Posted

技术标签:

【中文标题】java中使用wait()和notify()的简单场景【英文标题】:A simple scenario using wait() and notify() in java 【发布时间】:2011-02-01 23:05:37 【问题描述】:

我能否获得一个完整的简单场景,即建议如何使用它的教程,特别是与队列一起使用?

【问题讨论】:

【参考方案1】:

你看过这个Java Tutorial吗?

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

至少我是这样做的。我不是并发专家,所以我尽量避免手动处理线程。

【讨论】:

【参考方案2】:

即使你特意要求wait()notify(),我觉得这句话还是很重要的:

Josh Bloch,Effective Java 2nd Edition,第 69 条:首选并发实用程序而不是 waitnotify(强调他的):

考虑到正确使用waitnotify 的难度,您应该改用更高级别的并发实用程序 [...] 直接使用waitnotify 就像与java.util.concurrent 提供的高级语言相比,使用“并发汇编语言”进行编程。 很少有理由在新代码中使用waitnotify

【讨论】:

java.util.concurrent 包中提供的 BlockingQueueS 不是持久的。当队列需要持久化时,我们可以使用什么?即,如果系统在队列中有 20 个项目出现故障,我需要在系统重新启动时出现这些项目。由于 java.util.concurrent 队列似乎都只是“在内存中”,有什么办法可以按原样使用/被黑客入侵/覆盖以提供具有持久性的实现? 或许可以提供后备队列?即我们将提供一个持久的 Queue 接口实现。 在这种情况下很好地提到你永远不需要再次使用notify()wait()【参考方案3】:

不是队列示例,但非常简单:)

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) 您应该检查 获得后再次条件 同步锁。让我们说披萨 不要永远持续下去。你醒了, 比萨排队,但事实并非如此 对每个人都足够了。如果你不 检查,你可能会吃纸! :) (可能更好的例子是 while(!pizzaExists) wait();

2) 在调用 wait/nofity 之前,您必须持有锁(同步)。线程也必须在唤醒之前获取锁。

3) 尽量避免在同步块中获取任何锁,并努力不调用外来方法(您不确定它们在做什么的方法)。如果必须,请务必采取措施避免死锁。

4) 小心使用 notify()。坚持使用 notifyAll(),直到你知道自己在做什么。

5)最后但同样重要的是,请阅读Java Concurrency in Practice!

【讨论】:

能否请您详细说明为什么不使用“if(!pizzaArrived) wait(); ”? @Everyone:添加了一些解释。 HTH。 为什么使用pizzaArrivedflag?如果在没有调用notify 的情况下更改了标志,它不会有任何效果。也只需使用waitnotify 调用示例即可。 我不明白 - 线程 1 执行eatPizza() 方法并进入顶部同步块,并在 MyHouse 类上同步。还没有比萨饼到,所以它只是等待。现在线程 2 尝试通过调用 PizzaGuy() 方法来交付披萨;但不能因为线程 1 已经拥有锁并且它没有放弃它(它一直在等待)。实际上结果是死锁 - 线程 1 正在等待线程 2 执行 notifyAll() 方法,而线程 2 正在等待线程 1 放弃对 MyHouse 类的锁定......我错过了什么在这里? 不行,当一个变量被synchronized关键字保护时,声明变量volatile是多余的,建议避免使用,以免混淆@mrida【参考方案4】:

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() 调用在同一个对象上同步)。造成这种情况的原因(除了标准线程安全问题)是由于被称为丢失信号的东西。

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

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

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


正如其他一些答案所提到的,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();
        
    


当然,如果您确实需要阻塞队列,那么您应该使用BlockingQueue 接口的实现。

此外,对于此类内容,我强烈推荐 Java Concurrency in Practice,因为它涵盖了您可能想了解的有关并发相关问题和解决方案的所有信息。

【讨论】:

@greuze, notify 只唤醒一个线程。如果两个消费者线程竞争删除一个元素,一个通知可能会唤醒另一个消费者线程,后者无法对此做任何事情并会重新进入睡眠状态(而不是生产者,我们希望它会插入一个新元素。)因为生产者线程没有被唤醒,没有插入任何东西,现在所有三个线程都将无限期地休眠。我删除了我之前的评论,因为它(错误地)说虚假唤醒是问题的原因(不是。) @finnw 据我所知,您发现的问题可以通过使用 notifyAll() 来解决。我说的对吗? @Jared 在这里给出的示例非常好,但严重下降。代码中所有的方法都被标记为同步的,但是不能同时执行两个同步的方法,那图中怎么还有第二个线程呢。 @Brut3Forc3 您需要阅读 wait() 的 javadoc:它说:线程释放此监视器的所有权。所以,只要调用了wait(),监视器就被释放,另一个线程可以执行队列的另一个同步方法。 @JBNizet。 “这方面的一个例子是,当队列恰好满时,一个线程可能会调用 put(),然后它会检查条件,看到队列已满,但是在它可以阻止另一个线程之前被调度”。这里怎么来如果尚未调用 wait,则安排第二个线程。【参考方案5】:

例子

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
     


【讨论】:

【参考方案6】:

线程中的 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());
   

【讨论】:

请仔细检查这一行if(arrayList.size() == 0),我认为这可能是一个错误。【参考方案7】:

问题要求等待()+通知()涉及队列(缓冲区)。首先想到的是使用缓冲区的生产者-消费者场景。

我们系统中的三个组件:

    队列 [缓冲区] - 线程之间共享的固定大小队列 生产者 - 线程产生/插入值到缓冲区 消费者 - 线程从缓冲区中消费/删除值

生产者线程: 生产者在缓冲区中插入值,直到缓冲区已满。 如果缓冲区已满,则生产者调用 wait() 并进入等待阶段,直到消费者唤醒它。

    static class Producer extends Thread 
    private Queue<Integer> queue;
    private int maxSize;

    public Producer(Queue<Integer> queue, int maxSize, String name) 
        super(name);
        this.queue = queue;
        this.maxSize = maxSize;
    

    @Override
    public void run() 
        while (true) 
            synchronized (queue) 
                if (queue.size() == maxSize) 
                    try 
                        System.out.println("Queue is full, " + "Producer thread waiting for " + "consumer to take something from queue");
                        queue.wait();
                     catch (Exception ex) 
                        ex.printStackTrace();
                    
                
                Random random = new Random();
                int i = random.nextInt();
                System.out.println(" ^^^ Producing value : " + i);
                queue.add(i);
                queue.notify();
            
            sleepRandom();
        
    

消费者线程: 消费者线程从缓冲区中删除值,直到缓冲区为空。 如果缓冲区为空,消费者调用wait()方法进入等待状态,直到生产者发送通知信号。

    static class Consumer extends Thread 
    private Queue<Integer> queue;
    private int maxSize;

    public Consumer(Queue<Integer> queue, int maxSize, String name) 
        super(name);
        this.queue = queue;
        this.maxSize = maxSize;
    

    @Override
    public void run() 
        Random random = new Random();
        while (true) 
            synchronized (queue) 
                if (queue.isEmpty()) 
                    System.out.println("Queue is empty," + "Consumer thread is waiting" + " for producer thread to put something in queue");
                    try 
                        queue.wait();
                     catch (Exception ex) 
                        ex.printStackTrace();
                    
                
                System.out.println(" vvv Consuming value : " + queue.remove());
                queue.notify();
            
            sleepRandom();
        
    

实用方法:

    public static void sleepRandom()
    Random random = new Random();
    try 
        Thread.sleep(random.nextInt(250));
     catch (InterruptedException e) 
        e.printStackTrace();
    

申请代码:

    public static void main(String args[]) 
    System.out.println("How to use wait and notify method in Java");
    System.out.println("Solving Producer Consumper Problem");
    Queue<Integer> buffer = new LinkedList<>();
    int maxSize = 10;
    Thread producer = new Producer(buffer, maxSize, "PRODUCER");
    Thread consumer = new Consumer(buffer, maxSize, "CONSUMER");
    producer.start();
    consumer.start();

示例输出:

 ^^^ Producing value : 1268801606
 vvv Consuming value : 1268801606
Queue is empty,Consumer thread is waiting for producer thread to put something in queue
 ^^^ Producing value : -191710046
 vvv Consuming value : -191710046
 ^^^ Producing value : -1096119803
 vvv Consuming value : -1096119803
 ^^^ Producing value : -1502054254
 vvv Consuming value : -1502054254
Queue is empty,Consumer thread is waiting for producer thread to put something in queue
 ^^^ Producing value : 408960851
 vvv Consuming value : 408960851
 ^^^ Producing value : 2140469519
 vvv Consuming value : 65361724
 ^^^ Producing value : 1844915867
 ^^^ Producing value : 1551384069
 ^^^ Producing value : -2112162412
 vvv Consuming value : -887946831
 vvv Consuming value : 1427122528
 ^^^ Producing value : -181736500
 ^^^ Producing value : -1603239584
 ^^^ Producing value : 175404355
 vvv Consuming value : 1356483172
 ^^^ Producing value : -1505603127
 vvv Consuming value : 267333829
 ^^^ Producing value : 1986055041
Queue is full, Producer thread waiting for consumer to take something from queue
 vvv Consuming value : -1289385327
 ^^^ Producing value : 58340504
 vvv Consuming value : 1244183136
 ^^^ Producing value : 1582191907
Queue is full, Producer thread waiting for consumer to take something from queue
 vvv Consuming value : 1401174346
 ^^^ Producing value : 1617821198
 vvv Consuming value : -1827889861
 vvv Consuming value : 2098088641

【讨论】:

以上是关于java中使用wait()和notify()的简单场景的主要内容,如果未能解决你的问题,请参考以下文章

java多线程wait,notify,countDownLatch的一些简单应用

Java中 wait和await notify和signal的区别

Java中wait()和notify()方法的使用

java同步中,为啥要wait,又notify谁?

Java 多线程编程之:notify 和 wait 用法

Java并发编程之二彻底理解Java的wait和Notify机制