2017-01-03 13 views
6

私は説明できないデーモンスレッドの不思議な振る舞いに直面しました。私は、最小限の完全かつ検証可能なサンプルに私のコードを削減しました:デーモンスレッドが完了しないうちにJVMが終了しない

public static void main(String[] args) throws InterruptedException { 

    Thread runner = new Thread(() -> { 

     final int SIZE = 350_000; 
     for (int i = 0; i < SIZE; i++) { 
      for (int j = i + 1; j < SIZE; j++) { 
       if (i*j == SIZE * SIZE - 1) { 
        return; 
       } 
      } 
     } 
    }); 

    runner.setDaemon(true); 
    runner.start(); 

    // Thread.sleep(1000); 
    System.out.println("Exiting."); 
} 

runnerスレッドによって実行されるコードは、私のボックスに終了するのに約12秒かかり、そして我々はそれが何をするかに興味を持っていないなら、私はちょうどコンピュータの時間を費やす必要があったからです。

このスニペットをそのまま実行すると、期待通りに機能します。開始直後に終了します。 Thread.sleep(1000)行のコメントを外してプログラムを実行すると、約12秒間動作してから「終了」を出力して終了します。

デーモンスレッドの仕組みを理解していれば、このコードは1秒間実行してから実行を終了することを期待していました。実行しているユーザースレッドはmain()メソッドで起動されたスレッドだけです(runnerはバックグラウンド・デーモン・スレッド)、1000ミリ秒が経過するとすぐに実行終了に達し、JVMを停止する必要があります。また、 "Exiting"は12秒後にのみ印刷され、プログラムの起動時には印刷されません。

は私が間違っていますか?どのようにして目的の動作を達成できますか(1秒間停止してから、ランナースレッドの動作とは独立して停止します)。

私は64ビットのOracle JDK 1.8.0_112をLinuxボックスで使用しています。これは、IDEまたはコマンドラインから起動した場合と同じ動作をします。それは、そのメインメソッドから戻る前

おかげで、 アンドレア

+0

これはよく知られていますが、私はThread.sleepとdeamonスレッドの背景情報があふれないだけの検索語を見つけるのに十分な記憶はありません。 : - \ – yshavit

+0

FWIW、これはあなたが私のボックス(Windows、jdk 1.8.0_31)で期待するように動作します。あなたのボックスはマルチCPUボックスですか? – user2189998

+2

スケジューラは、メインスレッドの実行を再開する前に12秒間デーモンスレッドを実行することができます。あなたが観察した行動を禁じるものは何もありません。 –

答えて

5

これは、ネストされたループからSafepointポーリングを削除したカウントループ最適化の結果かもしれません。 JVMの起動オプションに-XX:+UseCountedLoopSafepointフラグを追加してください。

+0

このオプションを使用すると、期待どおりに動作します。ありがとう! –

2

Thread#sleep(long)はメインスレッドを一時停止(すなわち、JVMは限りない非デーモンスレッドが生きていないとして実行プログラムを検討している前に)。スケジューラは、deamonスレッドとなる他の実行可能スレッドを自由に実行することができます。現状では、デーモンスレッドがメインスレッドで実行を終了する前に強制的にデーモンスレッドをプリエンプトする明白な理由はありません(まだスリープしています)ので、JVMはスケジュールを自由に継続できます。ただし、実行中のスレッドを一時停止し、実行可能な別の実行可能スレッドをスケジュールすることをいつでも選択できるため、再現性が保証されません。

ループ内にThread#yield()または#sleep(1)へのコールを挿入することによって、プリエンプションを強制できます。スニペットが速く終了してからループを終了する前にスニペットが表示されるようになるでしょう。

スレッドの状態とスケジューリングの詳細は、hereの概要をご覧ください。コメントを

更新

私はバックグラウンドスレッドでのコードを変更することはできませんが(必要条件である)ので、それはあまりにも(長い説明のかかる場合、私はそれを停止する方法を探していました私がやっているのはstackoverflow.com/questions/41226054/...です)。

それは法的にstop a running thread from withinにのみ可能ですので、あなたは通常、それがアボート条件ごとの繰り返しをテストする必要があり、条件が満たされた場合、run方法return;秒。アボート条件は、外部(!volatile caveat!)から設定されるブール値フラグと同じくらい簡単です。したがって、最も単純な解決策は、スリープ後にメインスレッドにそのようなフラグを設定させることです。

タイムアウトをサポートするExecutorServiceを使用している可能性があります。ScheduledExecutorServiceを含む例についてはthis Q & Aを参照してください。

まだSystem.out命令を実行する前にスケジューラが12秒間待機する方法を理解できません。

それは 12秒待っていない、それはJVMを停止しても安全かどう決定するときにデーモンであることが唯一のJVMに重要ので、完了までのデーモンスレッドで実行してみましょう。スケジューラの場合、スレッドの状態だけが重要で、主スレッドの1秒後に実行中のスレッド(デーモン)と実行可能スレッド(メイン)があり、実行中のスレッドが実行可能スレッドに優先して一時停止する。スレッドを切り替えることも計算上高価なので、スケジューラは何の示唆も欠けているかもしれません。切り替えの兆候は、睡眠と収量だけでなく、GCの実行と他の多くのものがあります。

+0

バックグラウンドスレッドのコードを変更することはできません(要件です)。もし私がそれを止める方法を探していたら、私はそれを止める方法を探していました。それは時間がかかりすぎます(私が行っていることの説明はhttps://stackoverflow.com/questions/41226054/killing-a-java-thread-in-testです)。それでも私はスケジューラがSystem.out命令を実行する前に12秒待つことを決めることができないと理解していません。 –

+0

@AndreaIaconoそれに応じて私の答えを更新しました。これは明確なことですか? – hiergiltdiestfu

+0

ExecutorServiceの使い方は同じですが、私はすでに試してみました。待機時間は12秒です。私は何年もスレッドを使用していましたが、通常、スケジューラはミリ秒や数十ミリ秒後にスレッドを切り替えます。この場合、スイッチは通常よりも1,000倍以上後に起こります。これは私にとっては不思議なことです。実際、JDK 7と6で試したところ、これは起こりません:システム。スレッドはrunnerスレッドの最後ではなく、sleep()の後に正しく出力されます。しかし、それらのJDKを使用しても、ユーザスレッドが終了してもデーモンスレッドはすべて実行されています。 –

関連する問題