web-dev-qa-db-ja.com

java.lang.IllegalMonitorStateException:wait()の前にオブジェクトがスレッドによってロックされていませんか?

私はプログレスダイアログを使用しています。ユーザーがprogressdialogを閉じたときにスレッドを停止する必要があります。

インナークラスで

class UpdateThread extends Thread{

    public  void run() {
        while (true){
            count=adapter.getCount();

            try {
               mHandler.post(  new Runnable() {
                    public  void run() {
                        Log.i(TAG,count+"count");
                        progressDialog.setMessage(count + "Device  found");
                    }
                });
                Thread.sleep(300);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

作成する

 updateThread=new UpdateThread();

 progressDialog= new ProgressDialog(GroupListActivity.this);
 synchronized (this) {
     updateThread.start();
 }

解雇

   progressDialog.setOnDismissListener(new DialogInterface.OnDismissListener() {
        @Override
        public  void onDismiss(DialogInterface dialog) {
            try {
                synchronized (this) {
                    updateThread.wait(300);
                }

            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            Log.i(TAG,"Thread is stopped");
        }
    });
40
Asthme

これは間違っています:

synchronized(foo) {
    foo.wait();
}

問題は、このスレッドを何で目覚めさせるのでしょうか?つまり、どのように保証他のスレッドがfoo.notify()を呼び出さないこと最初のスレッドがfoo.wait()を呼び出すか? fooオブジェクトは、notify呼び出しが最初に発生した場合に通知されたことを記憶しないため、これは重要です。 notify()が1つだけで、wait()の前に発生した場合、wait()は決して戻りません。

待機と通知の使用方法は次のとおりです。

private Queue<Product> q = ...;
private Object lock = new Object();

void produceSomething(...) {
    Product p = reallyProduceSomething();
    synchronized(lock) {
        q.add(p);
        lock.notify();
    }
}

void consumeSomething(...) {
    Product p = null;
    synchronized(lock) {
        while (q.peek() == null) {
            lock.wait();
        }
        p = q.remove();
    }
    reallyConsume(p);
}

この例で注意すべき最も重要なことは、条件の明示的なテスト(つまり、q.peek()!= null)があり、誰もロックをロックせずに条件を変更しないことです。

コンシューマーが最初に呼び出された場合、キューは空であることがわかり、待機します。プロデューサーがスリップして、製品をキューに追加し、コンシューマーがその通知を受信する準備ができるまでロックに通知できる瞬間はありません。

一方、プロデューサーが最初に呼び出された場合、コンシューマーはwait()を呼び出さないことが保証されます。

コンシューマー内のループは2つの理由で重要です。1つは、複数のコンシューマースレッドがある場合、1人のコンシューマーが通知を受信できるが、別のコンシューマーが忍び寄ってキューから製品を盗むことです。その場合に最初の消費者が行う唯一の合理的なことは、次の製品を再度待つことです。ループが重要であるもう1つの理由は、JavadocがObject.wait()がオブジェクトに通知されていない場合でも戻ることが許可されていると言うことです。それは「スプリアスウェイクアップ」と呼ばれ、それを処理する正しい方法は戻って再び待つことです。

注:ロックはprivateで、キューはprivateです。これにより、他のコンパイル単位がこのコンパイル単位の同期を妨害しないことが保証されます。

注:ロックは、キュー自体とは異なるオブジェクトです。これにより、このコンパイルユニットでの同期が、Queue実装が行う同期(ある場合)に干渉しないことが保証されます。


注:私の例では、ポイントを証明するために車輪を再発明しています。実際のコードでは、ArrayBlockingQueueのput()およびtake()メソッドを使用して、すべての待機と通知を処理します。

57
Solomon Slow

オブジェクトのロックを既に保持している場合にのみ、オブジェクトを待つことができます。

synchronized (updateThread) {
    updateThread.wait(300);
}

...しかし、私はあなたがロックで何を達成しようとしているのか本当に分かりません。

9
BarrySW19

本当に、synchronizedwaitを使用すべきではない場所で使用しようとしているようです。

スレッドが完了するのを本当に待ちたい場合は、このようなことをする必要があります

UpdateThreadで:

class UpdateThread extends Thread{
    public AtomicBoolean stopped = new AtomicBoolean(false);
    public  void run() {
        while (!stopped.get()){
    .....

あなたの作成で:

updateThread = new UpdateThread();
progressDialog = new ProgressDialog(GroupListActivity.this);
updateThread.start();    // no synchronization necessary

あなたの解雇で:

progressDialog.setOnDismissListener(new DialogInterface.OnDismissListener() {
        @Override
        public  void onDismiss(DialogInterface dialog) {
            try {
                updateThread.stopped.set(true);
                updateThread.join(300);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            Log.i(TAG,"Thread is stopped");
        }
    });

スレッドに終了条件を追加して、実際に停止することに注意してください(つまり、スレッドは継続して実行されます)。おそらく、終了条件を非公開にし、クリーンさのためのセッターを追加する必要があります。また、スレッドが完了するまで適切に待機するために join を使用しています。

0
Krease