2011-08-30 13 views
9

Java wait()/ join():これはデッドロックしないのはなぜですか?次の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'を読み込むことができるように 'synchronized'として宣言されます。そのwait()呼び出しの間、wait()のドキュメントに記載されているように、ロックは解放され、戻ったときに再取得されます。 1秒後、メインスレッドはshutdown()を実行します。これはもう一方のスレッドが読み込んでいるのと同時に、mustShutdownにアクセスしないように再び同期します。その後、notify()を介してもう一方のスレッドを起動し、join()を介してその完了を待ちます。

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

+0

このような副作用があるため、スレッドを直接拡張するため、直接眉をひそめます。スレッドでラップするRunnableを実装する必要があります。 –

答えて

7

(参加)方法は、内部(Threadオブジェクトの)ロックの解除になります待機()を呼び出します。

(参加のコードを参照してください)以下:あなたのコードがこれを見て、一般的には見られない理由

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

理由::あなたのコードはこれを見ていない理由は、一般的には観察されません、なぜなら、join()メソッドはのスレッドオブジェクト自体を待機し、結果としてスレッドオブジェクト自体のロックを放棄し、run()メソッドも同じThreadオブジェクトで同期するからです。

+1

それは確かにそれを説明します。しかし、PaŭloEbermannが指摘するように、ドキュメンテーションはこれについて何も言わない。私はロックを解放するjoin()に頼ることができますか? – jlh

+0

はい。私は思春期を打つので、コードは変わりません。心配しないでください。 –

+0

非常に良い!ありがとう、ありがとう! – jlh

1

Thread.joinの実装はwaitを使用してロックを解除します。これは、他のスレッドがロックを取得するのを妨げない理由です。 MyThreadのrunメソッドを実行する新しいスレッドでmainメソッドの結果でMyThreadスレッドを開始

:ここ

は、この例では何が起こるかのステップバイステップの説明です。メインスレッドは1秒間スリープし、新しいスレッドに起動してMyThreadオブジェクトのロックを取得するのに十分な時間を与えます。

新しいスレッドは、waitメソッドに入り、ロックを解除できます。この時点で新しいスレッドは休止状態になり、起動するまでロックを再度取得しようとはしません。スレッドはまだwaitメソッドからを返しません。

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

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

新しいスレッドが終了すると、ロックを待機しているすべてのスレッドが通知を受信します。これにより、メインスレッドが起動し、ロックを取得して新しいスレッドが停止していることを確認した後、結合メソッドを終了して実行を終了します。

/** 
* 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; 
    } 
} 
} 
0

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

このから学ぶ:

  • は、あなたのスレッドオブジェクトに渡されたRunnable実装を使用する代わりに、Threadをサブクラス化していません。
  • 「所有していない」オブジェクトを同期/待機/通知しません。それ以外の誰が同期/待機/通知するか分かりません。
+0

スレッドをサブクラス化する代わりにRunnableを実装するこの特定の状況では、Threadはもっと複雑に思えます... MyThreadだけでsynchronized、wait()、join()、notify()を使用します。メインクラスは決してそれを使用しません。 (メインスレッドではありますが)2番目の箇条書きについてはわかりません。 – jlh

+0

ポイントは、 'join'が内部的に' wait() 'を使用していることです。期待しないかもしれません。私。 'MyThread'オブジェクトのモニターは2つの目的のために使用されます:独自の実行/シャットダウンサイクルを同期させ、内部スレッド管理を同期させる。 –

関連する問題