ホームページ  >  記事  >  Java  >  wait、notify、notifyAll の正しい使用法

wait、notify、notifyAll の正しい使用法

阿神
阿神オリジナル
2017-03-18 10:09:042139ブラウズ

Java では、waitnotify、および notifyAll を使用してスレッド間の通信を実装できます。 。たとえば、Java プログラムにプロデューサとコンシューマの 2 つのスレッドがある場合、キュー バッファ内に消費されるコンテンツ (null の場合ではない) があるため、プロデューサはコンシューマにデータの消費を開始するように通知できます。したがって、コンシューマーは、一部のデータを消費した後、バッファーがもういっぱいではないため、追加のデータの生成を開始できることをプロデューサーに通知できます。

wait、notify、notifyAll は、マルチスレッドでよく使用されるこれらの予約キーワードですが、多くの場合、実際の開発では真剣に考慮されません。この記事では、これらのキーワードの使用方法について説明します。

Java では、wait、notify、notifyAll を使用してスレッド間の通信を実現できます。 。たとえば、Java プログラムにプロデューサとコンシューマの 2 つのスレッドがある場合、キュー バッファ内に消費されるコンテンツ (null の場合ではない) があるため、プロデューサはコンシューマにデータの消費を開始するように通知できます。したがって、コンシューマーは、一部のデータを消費した後、バッファーがもういっぱいではないため、追加のデータの生成を開始できることをプロデューサーに通知できます。

wait() を使用して、特定の条件下でスレッドを一時停止できます。たとえば、プロデューサー/コンシューマー モデルでは、バッファーがいっぱいでコンシューマー スレッドが空の場合、プロデューサー スレッドは一時停止する必要があります。一部のスレッドが特定の条件がトリガーされるのを待っている場合、それらの条件が true になったときに、notify および NoticeAll を使用して、待機中のスレッドに実行を再開するよう通知できます。違いは、notify は 1 つのスレッドにのみ通知し、どのスレッドが通知を受け取るかはわかりませんが、notifyAll は待機中のすべてのスレッドに通知することです。つまり、セマフォを待機しているスレッドが 1 つだけの場合、notify と NoticeAll の両方がこのスレッドに通知します。ただし、複数のスレッドがこのセマフォを待機している場合、notify はそのうちの 1 つのスレッドにのみ通知し、他のスレッドは通知を受信せず、notifyAll は待機中のすべてのスレッドを起動します。

この記事では、wait、notify、notifyAll を使用してスレッド間の通信を実装し、プロデューサーとコンシューマーの問題を解決する方法を学びます。 Java のマルチスレッド同期の問題について詳しく知りたい場合は、Brian Goetz 著の『Java Concurrency in Practice | Java Concurrency Practice』を読むことを強くお勧めします。この本を読まなければ、Java マルチスレッドへの取り組みは完了しません。これは、Java 開発者向けに私が最も推奨する本の 1 つです。

Waitの使い方

waitとnotifyの概念は非常に基本的で、どちらもObjectクラスの関数ですが、それらを使用してコードを記述するのは簡単ではありません。面接中に候補者にコードを手書きし、生産者と消費者の問題を解決するために待機と通知を使用するように依頼した場合、ほとんどの候補者が迷ったり、同期キーワードを間違って使用するなどの間違いを犯したりすることはほぼ確実です。正しいオブジェクトで wait を使用していないか、標準コードのアプローチに従っていません。正直に言うと、この問題は、あまり使用しないプログラマーにとっては本当に頭の痛い問題です。

最初の質問は、コード内で wait() をどのように使用するかということです。 wait() は Thread クラスの関数ではないため、Thread.call() は使用できません。実際、多くの Java プログラマはこのように書くことを好みます。なぜなら、彼らは Thread.sleep() の使用に慣れているため、同じ目的を達成するために wait() を使用しようとしますが、これでは解決できないことがすぐにわかります。問題はスムーズに。正しい方法は、複数のスレッド間で共有されるオブジェクトで wait を使用することです。プロデューサーとコンシューマーの問題では、この共有オブジェクトはバッファー キューです。

2 番目の質問は、同期された関数またはオブジェクトで wait を呼び出す必要があるため、どのオブジェクトを同期する必要があるかということです。答えは、ロックしたいオブジェクト、つまり複数のスレッド間で共有されるオブジェクトを同期する必要があるということです。プロデューサーとコンシューマーの問題で同期する必要があるのはバッファ キューです。 (英語の原文に何か問題があると思います...文末に疑問符があってはなりません。そうしないと意味が通じません...)

如何在 Java 中正确使用 wait, notify 和 notifyAll – 以生产者消费者模型为例

常に電話して待ってください。 If ステートメントではなく、ループ内で通知します

现在你知道wait应该永远在被synchronized的背景下和那个被多线程共享的对象上调用,下一个一定要记住的问题就是,你应该永远在 while循环,而不是if语句中调用wait。因为线程是在某些条件下等待的——在我们的例子里,即“如果缓冲区队列是满的话,那么生产者线程应该等 待”,你可能直觉就会写一个if语句。但if语句存在一些微妙的小问题,导致即使条件没被满足,你的线程你也有可能被错误地唤醒。所以如果你不在线程被唤 醒后再次使用while循环检查唤醒条件是否被满足,你的程序就有可能会出错——例如在缓冲区为满的时候生产者继续生成数据,或者缓冲区为空的时候消费者 开始小号数据。所以记住,永远在while循环而不是if语句中使用wait!我会推荐阅读《Effective Java》,这是关于如何正确使用wait和notify的最好的参考资料。

基于以上认知,下面这个是使用wait和notify函数的规范代码模板:

// The standard idiom for calling the wait method in Java 
synchronized (sharedObject) { 
    while (condition) { 
sharedObject.wait(); 
        // (Releases lock, and reacquires on wakeup) 
    } 
    // do action based upon condition e.g. take or put into queue 
}

就像我之前说的一样,在while循环里使用wait的目的,是在线程被唤醒的前后都持续检查条件是否被满足。如果条件并未改变,wait被调用之前notify的唤醒通知就来了,那么这个线程并不能保证被唤醒,有可能会导致死锁问题。

Java wait(), notify(), notifyAll() 范例

下面我们提供一个使用wait和notify的范例程序。在这个程序里,我们使用了上文所述的一些代码规范。我们有两个线程,分别名为 PRODUCER(生产者)和CONSUMER(消费者),他们分别继承了了Producer和Consumer类,而Producer和 Consumer都继承了Thread类。Producer和Consumer想要实现的代码逻辑都在run()函数内。Main线程开始了生产者和消费 者线程,并声明了一个LinkedList作为缓冲区队列(在Java中,LinkedList实现了队列的接口)。生产者在无限循环中持续往 LinkedList里插入随机整数直到LinkedList满。我们在while(queue.size ==  maxSize)循环语句中检查这个条件。请注意到我们在做这个检查条件之前已经在队列对象上使用了synchronized关键词,因而其它线程不能在 我们检查条件时改变这个队列。如果队列满了,那么PRODUCER线程会在CONSUMER线程消耗掉队列里的任意一个整数,并用notify来通知 PRODUCER线程之前持续等待。在我们的例子中,wait和notify都是使用在同一个共享对象上的。

import java.util.LinkedList; 
import java.util.Queue; 
import java.util.Random; 
/** 
* Simple Java program to demonstrate How to use wait, notify and notifyAll() 
* method in Java by solving producer consumer problem. 
* 
* @author Javin Paul 
*/ 
public class ProducerConsumerInJava { 
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(); } 
} 
/** 
* Producer Thread will keep producing values for Consumer 
* to consumer. It will use wait() method when Queue is full 
* and use notify() method to send notification to Consumer 
* Thread. 
* 
* @author WINDOWS 8 
* 
*/ 
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) { 
      while (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.notifyAll(); 
      } 
     } 
    } 
   } 
/** 
* Consumer Thread will consumer values form shared queue. 
* It will also use wait() method to wait if queue is 
* empty. It will also use notify method to send 
* notification to producer thread after consuming values 
* from queue. 
* 
* @author WINDOWS 8 
* 
*/ 
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() { 
   while (true) { 
    synchronized (queue) { 
     while (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("Consuming value : " + queue.remove()); queue.notifyAll(); 
    } 
   } 
  } 
}

如何在 Java 中正确使用 wait, notify 和 notifyAll – 以生产者消费者模型为例

为了更好地理解这个程序,我建议你在debug模式里跑这个程序。一旦你在debug模式下启动程序,它会停止在PRODUCER或者 CONSUMER线程上,取决于哪个线程占据了CPU。因为两个线程都有wait()的条件,它们一定会停止,然后你就可以跑这个程序然后看发生什么了 (很有可能它就会输出我们以上展示的内容)。你也可以使用Eclipse里的Step into和Step over按钮来更好地理解多线程间发生的事情。

本文重点:

1. 你可以使用wait和notify函数来实现线程间通信。你可以用它们来实现多线程(>3)之间的通信。

2. 永远在synchronized的函数或对象里使用wait、notify和notifyAll,不然Java虚拟机会生成 IllegalMonitorStateException。

3. 永远在while循环里而不是if语句下使用wait。这样,循环会在线程睡眠前后都检查wait的条件,并在条件实际上并未改变的情况下处理唤醒通知。

4. 永远在多线程间共享的对象(在生产者消费者模型里即缓冲区队列)上使用wait。

5. 基于前文提及的理由,更倾向用 notifyAll(),而不是 notify()。

如何在 Java 中正确使用 wait, notify 和 notifyAll – 以生产者消费者模型为例

这是关于Java里如何使用wait,  notify和notifyAll的所有重点啦。你应该只在你知道自己要做什么的情况下使用这些函数,不然Java里还有很多其它的用来解决同步问题的方 案。例如,如果你想使用生产者消费者模型的话,你也可以使用BlockingQueue,它会帮你处理所有的线程安全问题和流程控制。如果你想要某一个线 程等待另一个线程做出反馈再继续运行,你也可以使用CycliBarrier或者CountDownLatch。如果你只是想保护某一个资源的话,你也可 以使用Semaphore。

相关文章:

JavaのnotifyとnotifyAllの比較詳細な紹介

Java同時実行におけるスレッド間のコラボレーションの2つの方法: wait、notify、notifyAll、Condition

例を通してnotify()とnotifyAll()の本質的な違いについて説明します

以上がwait、notify、notifyAll の正しい使用法の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。