Java 5:java.util.concurrent.FutureTask-cancel()およびdone()のセマンティクス
-
22-07-2019 - |
質問
現在、FutureTasksとExecutorsを使用して、マルチスレッド環境で厄介なバグを探しています。基本的な考え方は、テーブルに表示される結果を計算する個別のFutureTasksを固定数のスレッドに実行させることです(ここでのGUIの側面は気にしないでください)。
これを長い間見てきましたが、私の正気を疑い始めています。
このコードを検討してください:
public class MyTask extends FutureTask<Result> {
private String cellId;
...
protected void done() {
if (isCancelled()) return;
try {
Result r = get(); // should not wait, because we are done
... // some processing with r
sendMessage(cellId, r);
} catch (ExecutionException e) { // thrown from get
...
} catch (InterruptedException e) { // thrown from get
...
}
}
...
}
MyTaskのインスタンスを処理するエグゼキュータによってdone()
が呼び出されると、タスクがキャンセルされたため、そこに到達したかどうかを確認します。その場合、特にsendMessage()
を呼び出さないで、残りのすべてのアクティビティをスキップします。
FutureTask.done()のドキュメントには次のように書かれています:
このタスクがisDone状態に移行するときに呼び出される保護されたメソッド(通常またはキャンセルを介して)。デフォルトの実装は何もしません。サブクラスはこのメソッドをオーバーライドして、完了コールバックを呼び出したり、簿記を実行したりできます。このメソッドの実装内でステータスを照会して、このタスクがキャンセルされたかどうかを判断できることに注意してください。 ( APIリファレンス)
しかし、FutureTask
のドキュメントから得られないのは、セマンティクス while isCancelled()
の実行中です。最初にcancel()
チェックに合格したが、その直後に他のスレッドがisCancelled() == true
メソッドを呼び出すとどうなりますか?それは私の仕事の心を変え、それ以降にisDone()
を返信させますか?
もしそうなら、メッセージが送信されたかどうかを後でどのように知ることができますか? <=>を見ると、タスクの実行が完了したことがわかりますが、<=>も同様であったため、メッセージを時間内に送信できたかどうかわかりませんでした。
これは明らかなことかもしれませんが、今はあまり見ていません。
解決
APIから(エンファシスマイニング):
public boolean cancel(boolean mayInterruptIfRunning)
インターフェースからコピーされた説明:Future
このタスクの実行をキャンセルしようとします。 タスクが既に完了している場合、すでにキャンセルされている場合、または他の何らかの理由でキャンセルできない場合、この試行は失敗します。
したがって、FutureTaskは、isDoneステージに移行したときにタスクをキャンセルできないという前提で動作しています。
他のヒント
< => は、特定のインスタンスに対して複数回呼び出されることはなく、1つの理由でのみ呼び出されます-FutureTask#done()
エラーの有無にかかわらず完了、またはrun()
前のイベントが発生する前に実行された。これらの結果のいずれかによる完了の記録は、ラッチングです。 cancel()
完了した理由は、競合するイベントが<!> quot;同時に発生しているように見えても、変更することはできません。<!> quot;
したがって、FutureTask
内では、isCancelled()
またはisDone()
のいずれか1つのみがtrueを返します。 set()
レポートがエラーであるか、正常に完了したかを区別することは困難です。 setException(Throwable)
またはAQS
を決定的にオーバーライドすることはできません。両方とも内部 get()
を使用して、値の生成の成功または例外の発生を記録する試行を継続するかどうかを決定します。いずれかのメソッドをオーバーライドしても、そのメソッドが呼び出されたことがわかるだけですが、基本実装によって下された決定を観察することはできません。いずれかのイベントが発生した場合、<!> quot; too late <!> quot; <!>#8212;たとえば、 after cancel <!>#8212;値または例外を記録しようとすると無視されます。
実装を検討して、エラーからキャンセルされなかった成功結果を識別する唯一の方法は、弾丸を噛んで<=>を呼び出すことです。
メッセージを送信しない理由<!> quot; outside <!> quot; ExecutorService によって返される Future <!> lt; V <!> gt; オブジェクトの結果に基づいて、タスクの私はこのパターンを使用しましたが、うまく機能しているようです。 ExecutorService を使用して、多数の Callable <!> lt; V <!> gt; タスクを送信します。次に、プライマリタスクごとに、プライマリタスクの Future <!> lt; V <!> gt; を待機し、フォローアップアクション(メッセージの送信など)を行うセカンダリタスクを送信します) Future <!> lt; V <!> gt; が、プライマリタスクが正常に完了したことを示している場合のみ。このアプローチには当て推量はありません。 Future <!> lt; V <!> gt; .get()の呼び出しが戻ると、呼び出しを行わない限り、タスクが最終状態に到達したことが保証されます。タイムアウト引数を取る get のバージョン。
このアプローチをとる場合、2つの別個の ExecutorService インスタンスを使用する必要があります。1つはプライマリタスク用、もう1つはセカンダリインスタンス用です。これは、デッドロックを防ぐためです。スレッドプールのサイズが制限されている場合、セカンダリタスクを起動したり、プライマリタスクの起動をブロックしたりすることは望ましくありません。
FutureTask <!> lt; V <!> gt; を拡張する必要はまったくありません。タスクを Callable <!> lt; V <!> gt; オブジェクトとして実装するだけです。ただし、何らかの理由でタスクが Callable <!> lt; V <!> gt; コード内からキャンセルされたかどうかを検出する場合は、でスレッドの割り込みステータスを確認するだけです。 Thread.interrupted()。
cancel()
インスタンスがFuture
でハングしているときにdone()
を呼び出して何が起こるかを確認できる小さなテストケースを作成することをお勧めします。