今日も元気に真人間

好き: 中日 、アニソン 、中本 、IT 、乃木坂46(乃木坂って、どこ?、乃木坂工事中)

Javaの非同期処理とタイムアウト

処理に時間がかかるものは非同期で処理するというのはよくある話である。
そして処理に時間がかかるわけなので、何かあったときにほったらかしにしておくのは無責任であるということで、一定時間経過しても処理が終わっていなかったら処理を中断するというタイムアウトを設計したいということがある。

これをJavaで書こうとしたらやや面倒なことになった。相談したら「う~んJavaで書くのは難しいねぇ」という反応をされたので、言語として向いていないという説はある。それでもやろうとするならどうすればよいか。

 

 

■はじめに
以下のブログに、書きたいことがほぼ書かれている。

nagise.hatenablog.jp

特に重要なのは以下の記述である。

Threadのrun()中にいるとき、他のThreadからinterrupt()されたとき、実は通常は何も起きない。自動的にInterruptedExceptionが発生して中断してくれるわけではない。
InterruptedExceptionが発生して中断してくれるのはThread.sleep()やObject.wait()などの場合に限られる*2。それ以外の場合、プログラムが自分で割りこまれたのかをチェックしてその後の身の振り方を決めなくてはならない。

 

■Threadとinterrupt
Thread.interrupt()やFuture.calcel()はInterruptedExceptionをthrowするわけではない。単にinterrupted状態にするだけである。
何が厄介かというと、この手の話をしているときに8割方サンプルで出てくるThread.sleep()メソッドは、スレッドがinterruptedになったらInterruptedExceptionをthrowする。つまり、実際にはThread.sleep()がthrowしているのに、Thread.interrupt()やFuture.calcel()がInterruptedExceptionをthrowしているのだと勘違いされがちである。ググってみると、この勘違いをした情報はワンサカ出てくる。

↓は止まらない

while(true){
	try{
		// Thread.sleep()ではない何か
	}catch (Exception e) { // InterruptedExceptionをcatchしたい
		break;
	}
}

↓は止まる

while(true){
	if(Thread.interrupted()){
		break;
	}
	// Thread.sleep()ではない何か
}


すなわち、処理を止めたいときには、タスク側でマメにThread.interrupted()のチェックをする必要がある。

 

■非同期+タイムアウト
非同期というのは当然、処理を依頼したらそこで結果が返ってくる。
しかし、タイムアウト処理というのは一般に、指定された時間だけ待って、終わらなかった場合になんやかんやするという同期的な動きをすることが多い。

基本的なタイムアウトの仕方は以下のようなものである。

ExecutorService es = Executors.newSingleThreadExecutor();
Runnable task = new Task();
Future future = es.submit(task);

try {
	future.get(10, TimeUnit.SECONDS);
} catch (InterruptedException | ExecutionException | TimeoutException e) {
	e.printStackTrace();
}

 


Future.get()メソッドで別スレッド処理のタイムアウトを実装できる。が、これは同期的に待機するので、元々やりたかった非同期処理が実現できていない。

やるならこんな感じ。

ScheduledExecutorService es = Executors.newScheduledThreadPool(2);
Runnable task = new Task();
Future future = es.submit(task);
es.schedule(new Runnable() {
	public void run() {
		future.cancel(true);
	}
}, 10, TimeUnit.SECONDS);

calcelされるtaskでは、既に述べたように、適宜interruptedのチェックで処理中断の制御をする必要がある。

いずれにせよ、スレッドプールのクローズ(shutdownメソッドなど)などもする必要があるので、そこまで簡単な実装ではない。

 

■まとめ
・InterruptedExceptionは基本的にthrowされない
・Thread.interrupted()を自分で確認しないといけない
・非同期のタイムアウトを実装したければ、スレッドを止める用の別スレッドをさらに立てる必要がある