3

Javaでスレッドを一時停止および再開する方法を学ぼうとしています。「開始」と「停止」の 2 つのボタンがあるを使用しAppletています。implements Runnable

public void init(){
  th = new Thread(this);
  th.start();

  btn_increment = new Button("Start");
  btn_increment.addActionListener(new ActionListener(){
    public void actionPerformed(ActionEvent ev){
      th.notify();
    }
  });
  add(btn_increment);

  btn_decrement = new Button("Stop");
  btn_decrement.addActionListener(new ActionListener(){
    public void actionPerformed(ActionEvent ev){
      try{
        th.wait();
      } catch(InterruptedException e) {
        e.printStackTrace();
      }
    }
  });

  add(btn_decrement);                               
}

スレッドの実行方法:

public void run(){
  while(true){
    repaint();
    try{
      Thread.sleep(20);
    } catch(InterruptedException e) {
      e.printStackTrace();
    }
  }
}

スレッドを一時停止または再開しようとすると、例外がスローされます。

Exception in thread "AWT-EventQueue-1" java.lang.IllegalMonitorStateException

ノート:

suspend()非推奨のメソッドandを使用すると、前のコードは完全に実行されますが、ドキュメントでは、代わりに同期でandresume()を使用することが指摘されています。メソッドに単語を追加しようとしましたが、それでも例外がスローされます。notify()wait()synchronizedactionPerformed

これが機能しない理由と、同期の問題を解決する方法を誰かが説明してもらえますか? いくつかの説明ポイントが本当に役に立ちます ;)

4

3 に答える 3

10

あなたは仕組みを誤解してwait()います。オブジェクトを呼び出しwaitても、Threadそのスレッドは一時停止しません。代わりに、現在実行中のスレッドに、何か他のことが起こるのを待つように指示します。その理由を説明するには、少しバックアップして、synchronized実際に何が行われるかを説明する必要があります。

ブロックに入ると、オブジェクトに関連付けられたモニターsynchronizedを取得します。例えば、

synchronized(foo) {

オブジェクトに関連付けられたモニターを取得しますfoo

モニターを取得すると、同期ブロックを終了するまで、他のスレッドはそれを取得できません。これがどこにwaitあり、notify入ってきます。

wait保持しているモニターを一時的に解放するように現在実行中のスレッドに指示する Object クラスのメソッドです。これにより、他のスレッドが で同期できるようになりますfoo

foo.wait();

このスレッドは、他の誰かが呼び出しnotifyまたはnotifyAllオンにするfoo(またはスレッドが中断される) まで再開されません。それが発生すると、このスレッドはモニターの再取得を試みてからfoo続行します。他のスレッドがモニターの取得を待機している場合、それらのスレッドが最初に取得される可能性があることに注意してください。JVM がロックを配布する順序の保証はありません。wait()誰もnotifyまたはを呼び出さない場合、 は永久に待機することに注意してくださいnotifyAll。通常waitは、タイムアウトを取る別の形式を使用することをお勧めします。そのバージョンは、誰かが呼び出したとき、notifyまたはnotifyAllタイムアウトになったときに起動します。

したがって、待機を行うスレッドと通知を行う別のスレッドが必要です。waitとの両方notifyが、待機または通知しようとしているオブジェクトにモニターを保持する必要があります。これが、IllegalMonitorStateException が表示される理由です。

次の例が理解に役立ちます。

class RepaintScheduler implements Runnable {
    private boolean paused = false;
    private final Object LOCK = new Object();

    public void run() {
        while (true) {
            synchronized(LOCK) {
                if (paused) {
                    try {
                        LOCK.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                } else {
                    repaint();
                }
            }
            try {
                Thread.sleep(20);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public void pause() {
        synchronized(LOCK) {
            paused = true;
            LOCK.notifyAll();
        }
    }

    public void resume() {
        synchronized(LOCK) {
            paused = false;
            LOCK.notifyAll();
        }
    }
}

次に、アプレット コードで次の操作を実行できます。

public void init() {
    RepaintScheduler scheduler = new RepaintScheduler();
    // Add listeners that call scheduler.pause and scheduler.resume
    btn_increment.addActionListener(new ActionListener() {public void actionPerformed(ActionEvent e) {
        scheduler.resume();
    }});
    btn_decrement.addActionListener(new ActionListener() {public void actionPerformed(ActionEvent e) {
        scheduler.pause();
    }});
    // Now start everything up
    Thread t = new Thread(scheduler);
    t.start();
}

Applet クラスは、スケジューラが一時停止/再開する方法を気にせず、同期ブロックを持たないことに注意してください。

したがって、ここで考えられる一連のイベントは次のとおりです。

  • スレッド A が再描画スケジューラの実行を開始します。
  • スレッド A は 20 ミリ秒スリープ状態になります。
  • スレッド B (イベント ディスパッチ スレッド) はボタン クリックを受け取ります。「一時停止」を呼び出します。
  • スレッド B は LOCK でモニターを取得します。
  • スレッド B は「一時停止」変数を更新し、LOCK.notifyAll を呼び出します。
  • LOCK を待機しているスレッドがないため、興味深いことは何も起こりません。
  • スレッド B は LOCK でモニターを解放します。
  • スレッド A が起動し、再びループを通過します。
  • スレッド A は LOCK でモニターを取得します。
  • スレッド A は一時停止する必要があることを確認したため、LOCK.wait を呼び出します。
  • この時点でスレッド A は一時停止し、誰かが notifyAll を呼び出すのを待ちます。スレッド A は LOCK でモニターを解放します。
  • しばらくして、ユーザーは「再開」をクリックします。
  • スレッド B が scheduler.resume を呼び出します。
  • スレッド B は LOCK でモニターを取得します。
  • スレッド B は「一時停止」変数を更新し、LOCK.notifyAll を呼び出します。
  • スレッド A は「notifyAll」を見て、ウェイクアップします。LOCKでモニターを取得しようとしますが、スレッドBによって保持されているため、スレッドAがブロックされます。
  • スレッド B は LOCK でモニターを解放します。
  • スレッド A はモニターを取得して続行します。

それはすべて意味がありますか?

個別の LOCK 変数を持つ必要はありません。Threadインスタンスで待機/通知を呼び出していないという事実を強調するために、これを行いました。同様に、RepaintScheduler 内のロジックは理想的ではありませんが、wait/notify の使用方法を説明するためのものです。

于 2012-11-11T19:17:39.777 に答える
3

notifyとだけを呼び出すことはできませんwaitあなたは何かを待たなければなりません。そして、 を呼び出す前にnotify、もう待つ必要がないようにする必要があります。

ブロックがまだ同期されていない場合は、設計に問題があります。

wait待つものがなければ、どうやって電話をかけることができますか? そして、チェックしていないのに、待つべきものがあることをどうやって知ることができますか? そして、それがまだ起こっているかどうかを制御するコードと同期せずに、どうすればチェックできるでしょうか?

notifyスレッドに通知する必要がある何かが起こったのでない限り、どうすれば電話をかけることができますか? そして、別のスレッドが気にかけている何かが、そのスレッドにそれを伝えるロックを保持していなければ、どうして起こったのでしょうか?

wait次のように使用する必要があります。

while (something_to_wait_for()) wait();

そして、something_to_wait_for同期によって保護されているものをチェックする必要があります。また、競合状態が発生するため、同期を行うことはできません。何かが返されたsomething_to_wait_for後、入る前に何かが発生した場合はどうなりますか? それからあなたはすでに起こったことを待っています!したがって、基本的に同期が必要です。最後に追加するだけでは、デザインが壊れています。something_to_wait_forwait

あなたの場合の解決策は、おそらく待機するものを追加することです。おそらく、単純なブール変数で十分です。次に、コードはwhile (should_wait) wait();should_wait = true;、およびshould_wait = false(); notifyAll()です。ブール値と/ロジックsynchronizedを保護する必要があります。waitnotify

于 2012-11-11T18:47:42.417 に答える
1

待機と通知を呼び出すには、スレッドで同期する必要があると思います。使ってみて

synchronized (th) {
    th.notify();
}

と同じwait()です。

于 2012-11-11T18:46:54.323 に答える