理解wait()和notify()

时间:2016-03-12 12:02:54

标签: java multithreading

我试图了解在访问共享资源或依赖其状态时使用wait()notify()来实现线程的必​​要性。

我看到的想法是监视对象并等待它们的可用性并在使用后释放它们以使它们可用于其他线程/方法,但为什么这些方法是必要的而不是仅仅将相关对象声明为 static volatile 以便其他线程在不调用这些方法的情况下了解状态的变化?

例如

在一家餐馆,有2位厨师。其中一位厨师是一位好厨师(更好的烹饪品质,......)并带有布尔isGoodCook = true,而第二位厨师是一位厨师,并带有布尔isGoodCook = false

一个厨师只有一次可以做饭的设备。 糟糕的厨师会在特定的时间内做饭(=烹饪时间),而优秀的厨师偶尔会来厨房来接管糟糕的厨师做饭的任务。这位优秀的厨师在他的烹饪过程中永远不会被打断,并且一旦他开始烹饪就会为他的整个烹饪时间做饭。

(只要好厨师做饭的一部分(=好厨师的烹饪时间),坏厨师就会停止烹饪)。

在好厨师停止做饭后,坏厨师必须再次开始准备饭菜。

private boolean cooking; //is equipment used at the moment
private boolean isGoodCook;
private boolean cookingDesire; //indicating to chef to stop cooking
private int cookingTime;


public CookingTask(boolean isGoodCook, int cookingTime)
{
    this.isGoodCook = isGoodCook;
    this.cookingTime = cookingTime;
}

public void run()
{  
    if(isGoodCook)
    {
        //notify actual cook to stop cooking
        cookingDesire = true; 
    }
    //wait til equipment to cook
    //is available
    while(cooking)
    {
        try 
        {
            wait();
        } catch (InterruptedException e) 
        {
            e.printStackTrace();
        }
    }
    //reserve equipment
    cooking = true;
    cookingDesire = false;
    //notify other threads (= bad cook)
    notifyAll();
    startCooking();
}

private void startCooking()
{
    for(int i = 0; i < cookingTime; cookingTime--)
    {
        try 
        {
            Thread.sleep(1000);
            //if good chef comes in
            if(cookingDesire)
            {
                //bad chef starts to pause
                startBreak();
            }
        }
        catch (InterruptedException e) 
        {
            e.printStackTrace();
        }
    }
    cooking = false;
}

public void startBreak()
{
    //bad chef stops cooking
    cooking = false;
    notifyAll();
    //measure break time of bad chef
    long stopCookingTime = System.currentTimeMillis();
    while(cookingTime > 0 && cooking)
    {
        try 
        {
            wait();
        } catch (InterruptedException e) 
        {
            e.printStackTrace();
        }
    }
    int pausedTime = toIntExact((System.currentTimeMillis() - stopCookingTime)/1000);
    //calculate remaining cookingTime
    cookingTime -= pausedTime;
    cooking = true;
    notifyAll();
}

也许有人有时间阅读并很快勾勒出我对多线程监控/ wait()notify()的误解,我很乐意欣赏它!

4 个答案:

答案 0 :(得分:4)

static 表示类的所有对象共享该数据。您如何认为静态字段可用于说明特定线程对象的状态?

我想有人可以摆脱等待/通知;以一种线程必须查询其他线程的属性的方式。但这意味着“活动”:“等待”线程必须进行轮询。当然,你不能不断进行民意调查,所以你希望它能在一段时间内入睡。这几乎......等待,但更复杂,因为你必须通过编写代码来管理所有微妙的细节。

使用wait / notify你有推送模型。如果一个线程需要等待;你告诉它这样做;然后,当时间到了,它会被唤醒。这是一个非常清晰,直截了当的语义。

所以,当你提出一个不同的模型来解决这个问题时;你真的要证明你的模型达到了同样的目标;除此之外,还要考虑该模型的其他好处。

答案 1 :(得分:3)

  

将相关对象声明为静态volatile,以便其他线程在不调用这些方法的情况下了解状态的变化?

静态和易失性的目的与线程在满足状态变化或条件时提供的等待通知机制完全不同。

静态 - 表示字段/方法在类而不是实例级别关联,并且不需要创建对象来使用它们。

易失性 - 指示JVM始终读取此字段的最新值,即保证可见线程中易变变量的变化,并避免缓存一致性问题。

等待并通知,它是线程使用的通信机制,我们将从生产者 - 消费者示例中看到它。

生产者将任务放入需要消费者消费/完成的队列中。

从消费者等待任务出现在队列中的场景开始(空队列)。生产者放置任务,然后通知任何等待的消费者。这会唤醒消费者并开始处理任务。没有该消费者必须继续轮询该任务的队列。

从生产者端,它继续将任务放入队列,直到队列已满,然后生成器等待,直到队列中的空间可用。消费者在接收任务并在队列中腾出一些空闲空间时可以通知生产者。如果没有这个生产者,必须定期轮询队列以获得可用空间。

所以wait-notify是线程用来在任何条件/状态变化之间进行通信的通信机制。

同样通过等待和通知,JMM提供内存可见性保证,在关系之前发生。

在类似的说明中,JMM保证在每次后续读取该字段之前发生对易失性字段的写入,但不要与线程提供的等待通知机制混淆。

以下是来自Java revisited link的图像,其中显示了使用wait notify的生产者 - 消费者模式。绿色虚线调用notify方法以在满足条件时唤醒等待的线程。如果对相关代码感兴趣,您可能需要浏览link

Java wait-notify with Producer consumer pattern

答案 2 :(得分:1)

由于两者都是出于不同的目的,让我们理解核心差异。

static:所有对象的一个​​变量副本。

volatile:从主内存而不是线程缓存中获取变量的值

在许多应用程序中,您需要为对象而不是单个副本复制所有对象。如果您的提议是处理多线程应用程序的方法,则不应该使用static volatile的多线程应用程序使用类级别副本来约束。

现在让我们在没有variable的情况下单独变得不稳定static。从主内存中获取最新值很好。但是,如果多个线程正在更改该值,则无法保证一致性。

以您的银行帐户为例。假设您的帐户余额为50美元。一个线程是从deposit()方法添加值。其他线程正在从withdrawal()方法中扣除该值。

Thread 1:从主内存而不是缓存获得50美元的余额值。存款发生25美元,余额现已变为75美元。假设存款未同步且未使用wait()和notify()。

public void deposit(){
     //get balance : 50
     // update : 75
     // print  : 25 ( if withdrawal completes before print statement and 
        after get balance statement  
}

Thread 2:从主内存而不是缓存获得50美元的余额值。提款发生了25美元,余额现在变为25美元。

public void withdrawal(){
     //get balance : 50
     // update : 25
     // print : 25
}

只是让variable变得不稳定并没有帮助。您必须同步数据以保持一致性。

看看下面的SE问题,以便更好地理解这些概念:

Difference between volatile and synchronized in Java

Volatile Vs Static in java

Static variable vs Volatile

答案 3 :(得分:1)

可能可能使用锁和/或volatile变量来执行等待&#34;等待&#34;和&#34;通知&#34;旨在完成,但它不会效率。作为预期用法的简单示例,请考虑消息传递队列和线程,其作用是读取消息并处理它们。如果线程没有处理消息并且队列为空,那么线程就不会有任何有用的东西。

有可能有一个消息处理线程,它只是获取一个锁,检查队列是否为空,如果没有短暂释放锁,然后重新获取它,再次检查队列是否为空,等等但是这样一个线程会要求CPU花费大量时间检查空队列中的待处理消息,否则可能会有其他更有用的CPU。

最简单的思考方式&#34;等待&#34;就像向Java虚拟机提供一个指示,一个线程已经确定不会有任何有用的东西,除非或直到其他人指示,通过&#34;通知&#34;,有趣的东西可能发生了。 JVM并不需要对此指示执行任何操作,但在大多数情况下,它将阻止执行“等待”的线程。从接收任何更多的CPU时间直到它等待的对象收到&#34;通知&#34;。该线程甚至可能在此之前接收CPU时间[例如在小型嵌入式系统中运行的JVM可能无法跟踪线程正在等待的对象,但可以合法地等待&#34;等待&#34;暂停一个线程直到下一个&#34;通知&#34;给予任何对象],但是在一个线程不必要地连续接收CPU时间之前,让一个线程不必要地在实际需要之前短暂地接收几次CPU时间。 >