Threadにはinterruptという名前が入ったメソッドが3つ用意されています.
Thread#interrupt()
Thread#isInterrupted()
-
Thread.interrupted()
ぱっと見はどれがどれか分かりにくいので,解説してみます.
基本:Thread.interrupt()
JavaでThreadを止めるためにはinterruptメソッドを使います.
Thread th = new Thread( ()-> {
try {
Thread.sleep(1000);
} catch(InterruptedException e){
System.out.println("Test1: Interrupted");
}
});
th.start();
Thread.sleep(500);
th.interrupt();
Test1: Interrupted
当然ですが,スレッドが始まってだいたい500msecぐらい後にメッセージが出力されます.
先にinterruptが呼ばれた場合
とはいえ,スレッドがいつ実行されるかは実行環境次第なので,もしかしたらスレッドが実行されるより前にth.interrupt()
が実行されるかもしれません.
明示的にそういう状況を作ってみます.
AtomicBoolean flag = new AtomicBoolean(true);
Thread th = new Thread( ()-> {
while(flag.get()){
// Wait
}
try {
Thread.sleep(1000);
} catch(InterruptedException e){
System.out.println("Test2: Interrupted");
}
});
th.start();
th.interrupt();
Thread.sleep(500);
flag.set(false);
スレッドはflagがfalseになるまで待機します.なので,th.interrupt()
が呼ばれたときにスレッドはwhileループの中で,Thread.sleep()
を実行しているわけではありません.
この実行結果は
Test2: Interrupted
ということで,Thread.sleep()
が呼ばれた直後にinterruptされます.
この挙動は,例えば**「Thread.sleep()
の実行中にinterruptされた場合と,それより以前にinterruptされた場合に処理を分けたい」**といった場合に問題となります.
isInterruptedの使用
ThreadクラスにはisInterrupted()というメソッドが用意されているので使ってみることにします.
AtomicBoolean flag = new AtomicBoolean(true);
Thread th = new Thread( ()-> {
while(flag.get()){
// Wait
}
System.out.println("Test3: "+Thread.currentThread().isInterrupted());
try {
Thread.sleep(1000);
} catch(InterruptedException e){
System.out.println("Test3: Interrupted");
}
});
th.start();
th.interrupt();
Thread.sleep(500);
flag.set(false);
isInterrupted()メソッドはThreadインスタンスに対して実行する必要があるため,Thread.currentThread()で現在のスレッドを取得します.
Test3: true
Test3: Interrupted
大方の予想通りtrue
が表示され,interruptされたことを判別できます.ただ,その後Thread.sleep()
の呼び出しでInterruptedExceptionがスローされます.
当然と言えば当然で,isInterrupted()メソッドはinterruptされたかどうかを確認するだけなので,その後の処理には関係ありません.
isInterrupted()メソッドがtrueだった場合はスレッドを終了すれば問題は無いのですが,処理を続行したい場合には問題となります.
まぁ,そもそも一度interruptされたスレッドを実行し続けるような設計がおかしいのですが,そういうクソ設計に出会うことも想定してみようと思います.
Thread.interrupted()メソッドの使用
さて,ここでThread.interrupted()
メソッドの登場となります.
AtomicBoolean flag = new AtomicBoolean(true);
Thread th = new Thread( ()-> {
while(flag.get()){
// Wait
}
System.out.println("Test4: "+Thread.interrupted());
try {
Thread.sleep(1000);
} catch(InterruptedException e){
System.out.println("Test4: Interrupted");
}
});
th.start();
th.interrupt();
Thread.sleep(500);
flag.set(false);
isInterruptedと違い,interruptedメソッドは現在のスレッドに対して実行されるstaticメソッドなので,Thread.currentThread()
メソッドを使用する必要はありません.
これを実行すると
Test4: true
ということで,Thread.sleep()
メソッドは無事に実行され,1秒待機した後にスレッドは終了します.
Thread.interrupted()
メソッドはスレッドがinterruptされたかどうかの真偽値を返し,その後状態をリセットするメソッドです.(interruptedという名前はどうにかならんかったのか)
そのため,Thread.interrupted()
を2度実行すると2度目はfalseが返ってきます.
この辺りの挙動を理解しないままThread.interrupted()
メソッドを使うのは危険なので,正直使わない方が良いと思います.
今回はThread.sleep()
メソッドに対するinterruptの例を示しましたが,実際にはThread#join()
やSemaphore#acquire()
など,様々なブロックする場面で使用します.スレッドを適切に削除するためにもinterruptメソッドは適切に使ってください.
おまけ
Thread th = new Thread( ()-> {
try {
ServerSocket s = new ServerSocket(0);
s.accept();
} catch(IOException e){
System.out.println("Test5: Interrupted");
}
});
th.start();
Thread.sleep(500);
th.interrupt();
この場合,interruptされません.I/O系は別です.ネットワーク系のタイムアウト実装はまたそのうち解説します.