web-dev-qa-db-ja.com

スレッドではないオブジェクトでwait()およびnotify()メソッドを呼び出すにはどうすればよいですか?

スレッドではないオブジェクトでwait()およびnotify()メソッドをどのように呼び出すことができますか?それは本当に理にかなっていないのですか?

ただし、2つのメソッドはすべてのJavaオブジェクトで使用可能です。誰かが説明を提供できますか?wait()およびnotify()

43
Alexander Mills

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

ロックは、保護されているデータ構造にあります。スレッドは、データ構造にアクセスするものです。ロックは、スレッドが安全でない方法でデータ構造にアクセスするのを防ぐために、データ構造オブジェクトにあります。

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

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

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

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

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

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

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

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

35
Nathan Hughes

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();である

24
Alex

静的Threadクラスメソッドsleep()を使用して、必要に応じてスレッドを一時停止できます。

public class Main {
    //some code here

    //Thre thread will sleep for 5sec.
    Thread.sleep(5000);   
}

一部のオブジェクトを停止する場合は、syncronizedブロック内でこのメソッドを呼び出す必要があります。

public class Main {

//some code

public void waitObject(Object object) throws InterruptedException {
    synchronized(object) {
        object.wait();
    }
}

public void notifyObject(Object object) throws InterruptedException {
    synchronized(object) {
        object.notify();
    }
}

}

追伸あなたの質問を間違って理解した場合、私は腹を立てます(英語は私のネイティブではありません)

5
Petr Shypila

洗面所という実際の例を使用して考えてください。オフィスで洗面所を使用する場合、使用後に洗面所に誰も来ないようにするための2つのオプションがあります。

  1. 洗面所のドアをロックして、他の人がドアを開けようとするときに他の人が使用していることを他の人に知らせる
  2. オフィスの各人に行き、椅子(またはテーブルなど)にロックして、洗面所に行きます。

どのオプションを選択しますか?

はい、Javalandでも同じです。

したがって、上記の話では、

  • 洗面所=ロックするオブジェクト(使用する必要があるのはあなただけ)
  • スタッフの同僚=締め出したい他のスレッド

したがって、実際の生活と同じように、プライベートなビジネスがあるときには、そのオブジェクトをロックします。そして、そのオブジェクトを使い終わったら、ロックを解除します!.

(はいはい、これは何が起こるかについての非常に簡単な説明です。もちろん実際の概念はこれとわずかに異なりますが、これは出発点です)

同期ブロック内にコードを配置する場合:

 sychronized(lock){...}

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

sychronized(this){...}

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

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

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

balance += amountToDeposit;
lock.signallAll;

ご覧のように、メソッドは待機し、通知は同期ブロックまたはメソッド内でのみ意味があります。

4
luke657

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

3
Nicolas HENAUX

実際には、waitnotifyメンバー関数はスレッドに属しているべきではなく、名前に属するべきものcondition variableposix thread に由来します。また、cppがこの概念をどのようにラップするか、専用クラス名 std :: condition_variable にラップする方法を見ることができます。

CppはJavaよりもカプセル化の方が優れていると思います。Javaこれをやりすぎると、概念が直接Objectクラスに入れられ、最初は人々を混乱させます。

2
jean
  1. 待機と通知は、通常のメソッドや同期ユーティリティではなく、Javaの2つのスレッド間の通信メカニズムです。また、このメカニズムがsynchronizedなどのJavaキーワードを介して利用できない場合、Objectクラスはすべてのオブジェクトで利用できるようにする正しい場所です。同期と待機通知は2つの異なる領域であり、それらが同一または関連していることを混同しないでください。同期は、相互排除を提供し、待機と通知中の競合状態のようなJavaクラスのスレッドセーフを保証することで、2つのスレッド間の通信メカニズムです。
  2. ロックはオブジェクトごとに使用可能になります。これは、待機および通知がスレッドクラスではなくオブジェクトクラスで宣言されるもう1つの理由です。
  3. Javaでコードの重要なセクションに入るために、スレッドはロックが必要であり、ロックを待機します。同期ブロック内にあるスレッドを知り、ロックを解除するように依頼する代わりに。このアナロジーは、Javaのスレッドではなくオブジェクトクラスにある待機および通知に適合します。

Analogy: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);
        }
    }
}

ソース

2
Premraj

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

1
chestre