web-dev-qa-db-ja.com

Java:wait()は同期ブロックからロックを解除しますか

Wait()はすべてのロックを解除するという印象を受けましたが、この投稿を見つけました

「同期メソッド内で待機を呼び出すことは、固有のロックを取得する簡単な方法です」

私が少し混乱していることを明確にしてください。

http://docs.Oracle.com/javase/tutorial/essential/concurrency/guardmeth.html

55
Abhijit

「同期メソッド内で待機を呼び出すことは、固有のロックを取得する簡単な方法です」

この文は誤りです。ドキュメントの誤りです。

enters同期メソッドの場合、スレッドは組み込みロックを取得します。同期メソッド内のスレッドはロックの所有者として設定され、[〜#〜] runnable [〜#〜]状態にあります。ロックされたメソッドに入ることを試みるスレッドは[〜#〜] blocked [〜#〜]になります。

スレッドがwaitを呼び出すと、現在のオブジェクトロックが解放され(他のオブジェクトからのすべてのロックが保持されます)、[〜#〜] waiting [〜#〜]状態になります。

同じオブジェクトで他のスレッドがnotifyまたはnotifyAllを呼び出すと、最初のスレッドは状態をWAITINGからBLOCKEDに変更します。Notifiedスレッドは自動的にロックを再取得したりRUNNABLEになったりしません。

WAITING状態とBLOCKED状態はどちらもスレッドの実行を妨げますが、それらは非常に異なります。

WAITINGスレッドは、他のスレッドからの通知によって明示的にBLOCKEDスレッドに変換する必要があります。

待機は直接実行可能になりません。

RUNNABLEスレッドが(モニターを終了するか待機することにより)ロックを解除すると、BLOCKEDスレッドの1つが自動的に代わりになります。

要約すると、スレッドは同期メソッドに入るとき、または同期メソッドに入るときにロックを取得しますafter待機。

public synchronized guardedJoy() {
    // must get lock before entering here
    while(!joy) {
        try {
            wait(); // releases lock here
            // must regain the lock to reentering here
        } catch (InterruptedException e) {}
    }
    System.out.println("Joy and efficiency have been achieved!");
}
144
cohadar

待機が実際にロックを解除することを示すために、小さなテストクラス(非常に汚いコード、すみません)を用意しました。

public class Test {
    public static void main(String[] args) throws Exception {
        testCuncurrency();
    }

    private static void testCuncurrency() throws InterruptedException {
        Object lock = new Object();
        Thread t1 = new Thread(new WaitTester(lock));
        Thread t2 = new Thread(new WaitTester(lock));
        t1.start();
        t2.start();
        Thread.sleep(15 * 1000);
        synchronized (lock) {
            System.out.println("Time: " + new Date().toString()+ ";" + "Notifying all");
            lock.notifyAll();
        }
    }

    private static class WaitTester implements Runnable {
        private Object lock;
        public WaitTester(Object lock) {
            this.lock = lock;
        }

        @Override
        public void run() {
            try {
                synchronized (lock) {
                    System.out.println(getTimeAndThreadName() + ":only one thread can be in synchronized block");
                    Thread.sleep(5 * 1000);

                    System.out.println(getTimeAndThreadName() + ":thread goes into waiting state and releases the lock");
                    lock.wait();

                    System.out.println(getTimeAndThreadName() + ":thread is awake and have reacquired the lock");

                    System.out.println(getTimeAndThreadName() + ":syncronized block have finished");
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    private static String getTimeAndThreadName() {
        return "Time: " + new Date().toString() + ";" + Thread.currentThread().getName();
    }
}

私のマシンでこのクラスを実行すると、次の結果が返されます。

Time: Tue Mar 29 09:16:37 EEST 2016;Thread-0:only one thread can be in synchronized block
Time: Tue Mar 29 09:16:42 EEST 2016;Thread-0:thread goes into waiting state and releases the lock
Time: Tue Mar 29 09:16:42 EEST 2016;Thread-1:only one thread can be in synchronized block
Time: Tue Mar 29 09:16:47 EEST 2016;Thread-1:thread goes into waiting state and releases the lock
Time: Tue Mar 29 09:16:52 EEST 2016;Notifying all
Time: Tue Mar 29 09:16:52 EEST 2016;Thread-1:thread is awake and have reacquired the lock
Time: Tue Mar 29 09:16:57 EEST 2016;Thread-1:syncronized block have finished
Time: Tue Mar 29 09:16:57 EEST 2016;Thread-0:thread is awake and have reacquired the lock
Time: Tue Mar 29 09:17:02 EEST 2016;Thread-0:syncronized block have finished

wait ::は_Java.lang.Object_クラスの一部であるため、このメソッドはオブジェクトでのみ呼び出すことができます。これを呼び出すには、そのオブジェクトでmonitor(lock)が必要です。それ以外の場合は、IllegalMonitorStateExceptionがスローされます。たとえば、Thread.currentThread()。wait()は、以下のコードでこの例外をスローします。

_   Example1
   public void doSomething() {                                          Line 1
        synchronized(lockObject) { //lock acquired                      Line 2
            lockObject.wait();     // NOT Thread.currentThread().wait() Line 3
        }
    }
_

これで、Line 3でwaitを呼び出すと、Line 2で取得したロックが解除されます。したがって、Line 1に入ってlockObjectのロックの取得を待機している他のスレッドは、このロックを取得して続行します。

それでは、この_Example2_;について考えてみましょう。ここでは_lockObject2_ロックのみが解放され、現在のスレッドは_lockObject1_ロックを保持しています。これはデッドロックにつながります。したがって、ユーザーはこの場合により注意する必要があります。

_   Example2 
        public void doSomething() {                                     Line 1
             synchronized(lockObject1) { //lock1 acquired               Line 2
                 synchronized(lockObject2) { //lock2 acquired           Line 3
                     lockObject2.wait();                                Line 4
                 }
             }
        }
_

この待機が_sleep, yield, or join_に置き換えられた場合、ロックを解除する機能はありません。待機している場合のみ、保持しているロックを解除できます。

静的APIであり、常にアクションがスレッド_t1_ではなくcurrentThreadで実行されるt1.sleep()/t1.yield()に注意してください。

次に、suspendとこれらのAPIの_sleep, yield, join_との違いを理解しましょう。 suspendは非推奨であるため、スレッドがロックを保持している状態を回避するため、未定義の時間、スレッドがサスペンド(実行状態ではない)状態になるとデッドロックが発生します。これは、他のAPIでも同じ動作です。

答えは、これらのapiがt1.suspend()を中断しているThread.currentThread()のような他のスレッドで中断/再開が実行されることです。したがって、ユーザーは、デッドロックを回避するためにこれらのAPIを呼び出す前にロックを保持しないように注意する必要があります。これは、suspendを呼び出す場合には当てはまりません。呼び出し先スレッドは、サスペンドを実行する呼び出し元スレッド(ロック)の状態を知らないため、非推奨です。

6

この声明は、その完全な文脈の中で見るべきだと思います。

スレッドがd.waitを呼び出すとき、dの組み込みロックを所有する必要があります。そうでない場合、エラーがスローされます。同期メソッド内で待機を呼び出すことは、固有のロックを取得する簡単な方法です。

私は彼らがこれを次のように簡素化すべきであることを理解しています:

synchronizedメソッドの呼び出しはオブジェクトのロックを取得します。synchronizedメソッド内にwait()呼び出しを配置するだけです。

2
Yogi