11

次のJavaコードがあるとします。

public class Test {

    static private class MyThread extends Thread {
        private boolean mustShutdown = false;

        @Override
        public synchronized void run() {
            // loop and do nothing, just wait until we must shut down
            while (!mustShutdown) {
                try {
                    wait();
                } catch (InterruptedException e) {
                    System.out.println("Exception on wait()");
                }
            }
        }

        public synchronized void shutdown() throws InterruptedException {
            // set flag for termination, notify the thread and wait for it to die
            mustShutdown = true;
            notify();
            join(); // lock still being held here, due to 'synchronized'
        }
    }

    public static void main(String[] args) {
        MyThread mt = new MyThread();
        mt.start();

        try {
            Thread.sleep(1000);
            mt.shutdown();
        } catch (InterruptedException e) {
            System.out.println("Exception in main()");
        }
    }
}

これを実行すると、1秒間待機してから、適切に終了します。しかし、それは私には予想外です。ここでデッドロックが発生することを期待しています。

私の推論は次のとおりです。新しく作成されたMyThreadはrun()を実行します。これは、「同期」として宣言されているため、wait()を呼び出して、「mustShutdown」を安全に読み取ることができます。そのwait()呼び出し中に、wait()のドキュメントで説明されているように、ロックが解放され、戻ったときに再取得されます。1秒後、メインスレッドはshutdown()を実行します。これは、他のスレッドによる読み取りと同時にmustShutdownにアクセスしないように再度同期されます。次に、notify()を介して他のスレッドをウェイクアップし、join()を介してその完了を待ちます。

しかし、私の意見では、他のスレッドがwait()から戻ることはできません。これは、戻る前にスレッドオブジェクトのロックを再取得する必要があるためです。shutdown()はjoin()内でロックを保持しているため、これを行うことはできません。それでも機能し、正しく終了するのはなぜですか?

4

3 に答える 3

9

join()メソッドは内部でwait()を呼び出します。これにより、(Threadオブジェクトの)ロックが解放されます。

以下のjoin()のコードを参照してください。

public final synchronized void join(long millis) 
    throws InterruptedException {
    ....
    if (millis == 0) {
       while (isAlive()) {
         wait(0);  //ends up releasing lock
       }
    }
    ....
}

コードがこれを認識し、一般的には表示されない理由::コードがこれを認識し、一般的に表示されない理由は、join()メソッドがThreadオブジェクト自体でwaits()を実行し、その結果、Threadオブジェクトのロックを放棄するためです。それ自体と、run()メソッドも同じThreadオブジェクトで同期するため、この予期しないシナリオが表示されます。

于 2011-08-30T16:00:26.287 に答える
3

Thread.joinの実装では、待機を使用します。これにより、ロックが解除されます。これが、他のスレッドがロックを取得するのを妨げない理由です。

この例で何が起こるかを段階的に説明します。

mainメソッドでMyThreadスレッドを開始すると、新しいスレッドがMyThreadrunメソッドを実行します。メインスレッドは1秒間スリープし、新しいスレッドを起動してMyThreadオブジェクトのロックを取得するための十分な時間を与えます。

その後、新しいスレッドはwaitメソッドに入り、そのロックを解放できます。この時点で、新しいスレッドは休止状態になり、ウェイクアップされるまでロックを再度取得しようとはしません。スレッドはまだwaitメソッドから戻りません。

この時点で、メインスレッドはスリープから復帰し、MyThreadオブジェクトでshutdownを呼び出します。新しいスレッドが待機を開始するとロックを解放したため、ロックの取得に問題はありません。メインスレッドの呼び出しは今すぐ通知します。joinメソッドに入ると、メインスレッドは新しいスレッドがまだ生きていることを確認してから待機し、ロックを解放します。

メインスレッドがロックを解除すると、通知が発生します。メインスレッドがnotifyを呼び出した時点で、新しいスレッドはロックの待機セットにあったため、新しいスレッドは通知を受信して​​ウェイクアップします。ロックを取得し、waitメソッドを終了し、runメソッドの実行を終了して、最終的にロックを解放できます。

新しいスレッドが終了すると、ロックを待機しているすべてのスレッドが通知を受信します。これによりメインスレッドがウェイクアップし、ロックを取得して新しいスレッドが停止していることを確認してから、joinメソッドを終了して実行を終了します。

/**
 * Waits at most <code>millis</code> milliseconds for this thread to 
 * die. A timeout of <code>0</code> means to wait forever. 
 *
 * @param      millis   the time to wait in milliseconds.
 * @exception  InterruptedException if any thread has interrupted
 *             the current thread.  The <i>interrupted status</i> of the
 *             current thread is cleared when this exception is thrown.
 */
public final synchronized void join(long millis) 
throws InterruptedException {
long base = System.currentTimeMillis();
long now = 0;

if (millis < 0) {
        throw new IllegalArgumentException("timeout value is negative");
}

if (millis == 0) {
    while (isAlive()) {
    wait(0);
    }
} else {
    while (isAlive()) {
    long delay = millis - now;
    if (delay <= 0) {
        break;
    }
    wait(delay);
    now = System.currentTimeMillis() - base;
    }
}
}
于 2011-08-30T15:58:26.893 に答える
0

他の回答を補足するために:join()APIドキュメントでロックを解放することについての言及がないため、この動作は実際には実装固有です。

これから学ぶ:

  • サブクラス化しないでください。Thread代わりRunnableに、スレッドオブジェクトに渡された実装を使用してください。
  • 「所有」していないオブジェクトを同期/待機/通知しないでください。たとえば、他に誰が同期/待機/通知するかわからない場合などです。
于 2011-08-30T16:07:31.363 に答える