50

スレッドではないオブジェクトでメソッドwait()とメソッドを呼び出すにはどうすればよいですか? notify()それは本当に意味がありませんね。

ただし、この 2 つのメソッドはすべての Java オブジェクトで使用できるため、当然のことです。誰かが説明を提供できますか?wait()と を使用してスレッド間で通信する方法がわかりませんnotify()

4

10 に答える 10

41

ロックとは、共有データを保護することです。

ロックは、保護されているデータ構造にかかっています。スレッドは、データ構造にアクセスするものです。ロックは、スレッドが安全でない方法でデータ構造にアクセスしないようにするために、データ構造オブジェクトに適用されます。

任意のオブジェクトを固有ロックとして使用できます ( と組み合わせて使用​​することを意味しますsynchronized)。このように、共有データにアクセスするメソッドに synchronized 修飾子を追加することで、任意のオブジェクトへのアクセスを保護できます。

waitおよびメソッドは、notifyロックとして使用されているオブジェクトに対して呼び出されます。ロックは共有通信ポイントです。

  • ロックを持つスレッドが呼び出すnotifyAllと、同じロックを待機している他のスレッドに通知されます。ロックを持つスレッドが呼び出すnotifyと、同じロックを待機しているスレッドの 1 つに通知されます。

  • ロックを持つスレッドが呼び出しwaitを行うと、スレッドはロックを解放し、a) 通知を受信するか、b) 任意にウェイクアップする (「偽のウェイクアップ」) まで休止状態になります。待機中のスレッドは、これら 2 つの理由のいずれかが原因でウェイクアップするまで待機の呼び出しでスタックしたままになります。その場合、スレッドは待機メソッドを終了する前にロックを再取得する必要があります。

保護されたブロックに関する Oracle チュートリアルを参照してください。Drop クラスは共有データ構造であり、Producer および Consumer ランナブルを使用するスレッドがそれにアクセスしています。Drop オブジェクトのロックは、スレッドが Drop オブジェクトのデータにアクセスする方法を制御します。

スレッドは JVM 実装でロックとして使用されるため、アプリケーション開発者はスレッドをロックとして使用しないようにすることをお勧めします。たとえば、Thread.join のドキュメントには次のように書かれています。

この実装は、this.isAlive を条件とする this.wait 呼び出しのループを使用します。スレッドが終了すると、 this.notifyAll メソッドが呼び出されます。アプリケーションが Thread インスタンスで wait、notify、または notifyAll を使用しないことをお勧めします。

Java 5 では、 を実装する明示的なロックが導入されjava.util.concurrent.locks.Lockました。これらは暗黙のロックよりも柔軟です。待機と通知 (待機とシグナル) に類似したメソッドがありますが、それらはロックではなく条件にあります。複数の条件を設定すると、特定の種類の通知を待機しているスレッドのみをターゲットにすることができます。

于 2013-04-24T16:33:01.390 に答える
25

とを使用wait()notify()てロジックを同期できます。例として

synchronized (lock) {
    lock.wait(); // Will block until lock.notify() is called on another thread.
}

// Somewhere else...
...
synchronized (lock) {
    lock.notify(); // Will wake up lock.wait()
}

lockクラスのメンバーとしてObject lock = new Object();

于 2013-04-24T16:25:19.883 に答える
4

同期ブロック内にコードを配置すると、次のようになります。

 sychronized(lock){...}

このブロック内にあるものを実行したいスレッドは、最初にオブジェクトのロックを取得し、一度に 1 つのスレッドだけが同じオブジェクトでロックされたコードを実行できます。どのオブジェクトもロックとして使用できますが、スコープに関連するオブジェクトを慎重に選択する必要があります。たとえば、アカウントに何かを追加する複数のスレッドがあり、それらすべてが次のようなブロック内にそれを担当するコードを持っている場合:

sychronized(this){...}

それらはすべて異なるオブジェクトでロックされているため、同期は行われません。代わりに、アカウント オブジェクトをロックとして使用する必要があります。これらのスレッドには、アカウントからの引き出し方法もあると考えてください。この場合、何かを引き出したいスレッドが空のアカウントに遭遇する状況が発生する可能性があります。デッドロックを回避するために、ある程度の資金が得られるまで待機し、ロックを他のスレッドに解放する必要があります。それが、wait メソッドと notify メソッドの目的です。この例では、空のアカウントに遭遇したスレッドはロックを解放し、入金を行うスレッドからのシグナルを待ちます。

while(balance < amountToWithdraw){
    lock.wait();
}

他のスレッドがお金を預けると、他のスレッドが同じロックを待っていることを知らせます。(もちろん、入金と出金を担当するコードは、これが機能し、データの破損を防ぐために、同じロックで同期する必要があります)。

balance += amountToDeposit;
lock.signallAll;

ご覧のとおり、メソッド wait と notify は、同期されたブロックまたはメソッド内でのみ意味があります。

于 2013-04-24T17:22:57.050 に答える
3

Java では、すべてのオブジェクトがこれら 2 つのメソッドを実装しています。明らかに、モニターがなければ、これら 2 つのメソッドは役に立ちません。

于 2013-04-24T16:26:56.987 に答える
2
  1. 待機と通知は、単なる通常のメソッドや同期ユーティリティではなく、Java の 2 つのスレッド間の通信メカニズムです。また、同期などの Java キーワードを介してこのメ​​カニズムを使用できない場合、Object クラスはすべてのオブジェクトでそれらを使用できるようにする正しい場所です。同期と待機通知は 2 つの異なる領域であり、それらが同じまたは関連していることを混同しないでください。同期は、相互排除を提供し、競合状態のような Java クラスのスレッド セーフを確保することであり、待機と通知は 2 つのスレッド間の通信メカニズムです。
  2. ロックはオブジェクトごとに利用可能になります。これは、wait と notify が Thread クラスではなく Object クラスで宣言されるもう 1 つの理由です。
  3. Java では、コードのクリティカル セクションに入るために、スレッドはロックを必要とし、ロックを待機します。どのスレッドがロックを保持しているかはわかりません。代わりに、ロックが何らかのスレッドによって保持されていることを知っているだけであり、どのスレッドかを知る代わりにロックを待機する必要があります。スレッドは同期ブロック内にあり、ロックを解放するように求めています。このアナロジーは、Java のスレッドではなく、オブジェクト クラスにある待機と通知に適合します。

アナロジー: Java スレッドはユーザーであり、トイレはスレッドが実行したいコードのブロックです。Java は、同期化された keywokd を使用して現在コードを実行しているスレッドのコードをロックし、それを使用したい他のスレッドを最初のスレッドが終了するまで待機させる方法を提供します。これらの他のスレッドは待機状態になります。スレッドを待機するためのキューがないため、Java はサービス ステーションほど公平ではありません。要求された順序に関係なく、待機中のスレッドのいずれかが次にモニターを取得する可能性があります。唯一の保証は、遅かれ早かれすべてのスレッドが監視対象のコードを使用できるようになることです。

ソース

次の生産者と消費者のコードを見ると:
sharedQueueオブジェクトはスレッド間のproducer and consumerスレッド間通信を行います。

import java.util.Vector;
import java.util.logging.Level;
import java.util.logging.Logger;

public class ProducerConsumerSolution {

    public static void main(String args[]) {
        Vector<Integer> sharedQueue = new Vector<Integer>();
        int size = 4;
        Thread prodThread = new Thread(new Producer(sharedQueue, size), "Producer");
        Thread consThread = new Thread(new Consumer(sharedQueue, size), "Consumer");
        prodThread.start();
        consThread.start();
    }
}

class Producer implements Runnable {

    private final Vector<Integer> sharedQueue;
    private final int SIZE;

    public Producer(Vector<Integer> sharedQueue, int size) {
        this.sharedQueue = sharedQueue;
        this.SIZE = size;
    }

    @Override
    public void run() {
        for (int i = 0; i < 7; i++) {
            System.out.println("Produced: " + i);
            try {
                produce(i);
            } catch (InterruptedException ex) {
                Logger.getLogger(Producer.class.getName()).log(Level.SEVERE, null, ex);
            }

        }
    }

    private void produce(int i) throws InterruptedException {

        // wait if queue is full
        while (sharedQueue.size() == SIZE) {
            synchronized (sharedQueue) {
                System.out.println("Queue is full " + Thread.currentThread().getName() + " is waiting , size: "
                        + sharedQueue.size());

                sharedQueue.wait();
            }
        }

        // producing element and notify consumers
        synchronized (sharedQueue) {
            sharedQueue.add(i);
            sharedQueue.notifyAll();
        }
    }
}

class Consumer implements Runnable {

    private final Vector<Integer> sharedQueue;
    private final int SIZE;

    public Consumer(Vector<Integer> sharedQueue, int size) {
        this.sharedQueue = sharedQueue;
        this.SIZE = size;
    }

    @Override
    public void run() {
        while (true) {
            try {
                System.out.println("Consumed: " + consume());
                Thread.sleep(50);
            } catch (InterruptedException ex) {
                Logger.getLogger(Consumer.class.getName()).log(Level.SEVERE, null, ex);
            }

        }
    }

    private int consume() throws InterruptedException {
        //wait if queue is empty
        while (sharedQueue.isEmpty()) {
            synchronized (sharedQueue) {
                System.out.println("Queue is empty " + Thread.currentThread().getName()
                                    + " is waiting , size: " + sharedQueue.size());

                sharedQueue.wait();
            }
        }

        //Otherwise consume element and notify waiting producer
        synchronized (sharedQueue) {
            sharedQueue.notifyAll();
            return (Integer) sharedQueue.remove(0);
        }
    }
}

ソース

于 2015-12-18T23:17:38.497 に答える
1

「このメソッドは、このオブジェクトのモニターの所有者であるスレッドによってのみ呼び出されるべきです。」したがって、オブジェクトのモニターであるスレッドがあることを確認する必要があると思います。

于 2013-04-24T16:29:34.703 に答える