回答:
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()
、キューがいっぱいになったときにスレッドが呼び出され、状態がチェックされ、キューがいっぱいであることを確認しますが、別のスレッドがブロックされる前にスケジュールされます。次に、この2番目のスレッドtake()
はキューの要素であり、キューがいっぱいではなくなったことを待機中のスレッドに通知します。ただし、最初のスレッドはすでに条件をチェックしているため、wait()
進行する可能性があっても、再スケジュールされた後に単に呼び出されます。
共有オブジェクトで同期することでtake()
、最初のスレッドが実際にブロックされるまで2番目のスレッドの呼び出しが進行できないため、この問題が発生しないようにすることができます。
次に、偽のウェイクアップと呼ばれる問題のため、チェックする条件をifステートメントではなく、whileループに入れる必要があります。ここで、待機中のスレッドを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並行性の実践を強くお勧めします。これは、並行性に関連する問題と解決策について知りたいすべてのものをカバーしているためです。
キューの例ではありませんが、非常に単純です:)
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(condition)を使用してください。
while(!pizzaExists){ wait(); }
。2)wait / nofityを呼び出す前に、ロックを保持(同期)する必要があります。スレッドは、起動する前にロックを取得する必要もあります。
3)同期されたブロック内でロックを取得しないようにし、エイリアンメソッド(それらが何をしているのかわからないメソッド)を呼び出さないように努めます。必要に応じて、デッドロックを回避するための対策を講じてください。
4)notify()に注意してください。何をしているのかがわかるまで、notifyAll()を使用してください。
5)最後に、しかし重要ではありませんが、Java Concurrency in Practiceを読んでください!
pizzaArrived
フラグを使用するのですか?フラグが呼び出されずに変更された場合、notify
効果はありません。また、with wait
and notify
callの例でも機能します。
synchronized
キーワードで保護されている場合、変数を宣言するのは冗長ですvolatile
。混乱を避けるために、変数を回避することをお勧めします@mrida
あなたが求めたにもかかわらず wait()
およびnotify()
具体的には、私はこの引用は、まだ十分に重要であると感じます。
Josh Bloch、Effective Java 2nd Edition、Item 69:Prefer concurrency Utilities towait
てnotify
(強調):
使用しての難しさを考える
wait
とnotify
、正しく、あなたではなく、より高いレベルの並行処理ユーティリティを使用する必要があります [...]使用wait
してnotify
直接することにより提供されるより高いレベルの言語と比較して、「並行処理アセンブリ言語」でプログラミングのようなものですjava.util.concurrent
。理由の使用に、これまでであれば、ほとんどありませんwait
し、notify
新しいコードで。
notify()
とwait()
再び
さらに、この種のものを実際のソフトウェアで操作することは絶対に避けてください。それを試してみると良いので、それが何であるかを理解してください。他の人のためのソフトウェアを構築している場合は、より高いレベルの抽象化と同期されたコレクションまたはJMSキューを使用することをお勧めします。
それは少なくとも私がすることです。私は並行処理の専門家ではないので、可能な限り手作業でスレッドを処理することは避けます。
例
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
}
}
スレッディングでの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)
、ここでは間違いかもしれません。
notify
1つのスレッドのみを起こします。2つのコンシューマスレッドが要素の削除を競合している場合、1つの通知が他のコンシューマスレッドを呼び起こす可能性があり、それはそれについて何もできず、スリープ状態に戻ります(プロデューサではなく、新しい要素を挿入することを期待していました)。プロデューサースレッドは起こされず、何も挿入されず、3つのスレッドすべてが無期限にスリープします。それは見せかけの起動が(。それはありません)問題の原因だったことを(誤って)言ったように私は私の以前のコメントを削除