More Related Content
Similar to 20130611 java concurrencyinpracticech7
Similar to 20130611 java concurrencyinpracticech7 (12)
20130611 java concurrencyinpracticech7
- 2. 第7章 キャンセルとシャットダウン
• キャンセルとインタラプションの仕組みの紹介
• タスクやサービスをキャンセルリクエストに対
応付けるプログラムの書き方
• インタラプションとは
– あるスレッドが別のスレッドに停止を求めることが
できる仕組み(Thread
interrupt())
– Javaにはスレッドを途中で止める仕組みはない
• 瞬時にスレッドを止めたいことはめったにない。
– 後始末をしてから止まるべき
- 3. 目次
• 7-‐1
タスクのキャンセル
– 7-‐1-‐1
インタラプション
– 7-‐1-‐2
インタラプションポリシー
– 7-‐1-‐3
インタラプションへの応答
– 7-‐1-‐4
例:実行時間の制限
– 7-‐1-‐5
Futureからキャンセルする
– 7-‐1-‐6
インタラプトできないブロッキングの扱い方
– 7-‐1-‐7
標準的でないキャンセルを newTaskFor
でカプセル化する
• 7-‐2
スレッドを使っているサービスを停止する
– 7-‐2-‐1
例:ログ記録サービス
– 7-‐2-‐2
ExecutorService
のシャットダウン
– 7-‐2-‐3
毒薬
– 7-‐2-‐4
例:1回かぎりの実行サービス
– 7-‐2-‐5
shutdownNowの制約
• 7-‐3
スレッドの異常終了を扱う
– 7-‐3-‐1
未捕捉例外ハンドラ
• 7-‐4
JVMのシャットダウン
– 7-‐4-‐1
シャットダウンフック
– 7-‐4-‐2
デーモンスレッド
– 7-‐4-‐3
ファイナライザ
- 4. 7-‐1
タスクのキャンセル
-‐
1
• キャンセルする理由
– ユーザーがキャンセルをリクエストした
• GUIアプリのキャンセルボタンをクリック
– 時間制限のある活動
• 時間内に最良の結果を返すアプリ
– アプリケーションイベント
• 複数のタスクがそれぞれ問題空間を探索しあるタスクが解
を見つけたら、他のタスクはキャンセル
– エラー
• クローラーのタスクのエラー
– シャットダウン
• 穏やかなシャットダウン、緊急のシャットダウン
- 5. 7-‐1
タスクのキャンセル
-‐
2
• スレッドを強制的に停止する安全な方法はな
い。
• 協力的な仕組み
1. 「キャンセルがリクエストされた」フラグの周期的
チェック(List
7-‐1)
1. Cancelledをチェック。
2. Cancelled
をvolaNleにする
- 6. 7-‐1
タスクのキャンセル
-‐
3
• List
7-‐2
素数生成クラス
– Sleepのインタラプトが発生してもfinallyで確実にキャンセル
• キャンセルされる側のキャンセルポリシー
– How,
when,
whatを定義
• How:
キャンセルをどうやって求めるか
• When:
キャンセルがリクエストされたことをいつチェックするか
• What:
キャンセルのリクエストに対してタスクはどんなアクションを実
行すべきか
– PrimeGeneratorのキャンセルポリシー
• How:
クライアントコードはcancelをコールしてキャンセルをリクエスト
する
• When:
PrimeGeneratorは素数が一つ見つかるたびにキャンセルを
チェックする
• What:
キャンセルがリクエストされたことを検出したら終了する
- 8. 7-‐1-‐1
インタラプション -‐
2
• Threadのインタラプション関連メソッド(List
7-‐4)
– interrupt()
• 目的のスレッドをインタラプト
– isInterrupted()
• 目的のスレッドのインタラプテッドステータスを返す
– interrupted()
• 現在のスレッド(このメソッドを呼んだスレッド)のインタラプテッドステータスをクリアし、その前の値を
返す。
• Thread
インタラプションの挙動
– スレッドをブロックしている時、インタラプテッドステータスをクリアし、InterruptedExcepNonを
投げる
– スレッドがブロックしていない時、インタラプテッドステータスがセットされ、それを調べるか、
調べないかはスレッドの自由。スティキーな状態。
→ interruptメソッドは単に、インタラプションをリクエストしたというメッセージを伝えるだけ。「あなたのご
都合のよろしいとき(キャンセルポイント)にお仕事を中断してください」
• Interruptedがtrueなら何かをすべき。
– InterruptedExcepNonを投げる。Interruptを呼び出し、ステータスを復元
- 9. 7-‐1-‐1
インタラプション -‐
3
• InterruptedExcepNonを投げる。Interruptを呼
び出し、ステータスを復元
• BrokenPrimeProducerはフラグの代わりにイン
タラプションを使ってキャンセルをリクエスト
(List
7-‐5)
– 2つのインタラプションのチェック
1. ブロックするputメソッドの中
2. ループの明示的なポーリング
→応答性を上げるために処理の前にインタラプションをチェック
するs
- 10. 7-‐1-‐2
インタラプションポリシー -‐
1
• スレッドがインタラプションリクエストをどう解釈す
るかという取り決め
• 例
– インタラプションが検出されたらいつ何をするか
– インタラプションに対してはどの仕事単位をアトミック
と見なすべきか
– どんなタイミングでインタラプションに応答すべきか
• スレッドはインタラプションポリシーを持つべき。
• 妥当なインタラプションポリシーはスレッドレベル
又は、サービスレベルのキャンセル
- 11. 7-‐1-‐2
インタラプションポリシー -‐
2
• インタラプションへの反応はタスクとスレッドで違う
• 1つのインタラプトリクエストの目的が複数あることもある
– スレッドプールのワーカースレッドにインタラプトする
1. 現在のタスクをキャンセルせよ
2. このワーカースレッドをシャットダウンせよ
– タスクは自分が所有するスレッドの中では実行されない
• サービスからスレッドを借りる
• スレッドを所有しないコードはインタラプテッドステータスを保全して
スレッドのオーナーがインタラプションに対応できるように注意すべ
き。
• インタラプションリクエストの検出時は、実行中の仕事を完了してイ
ンタラプションに対応すれば良い
• 単純にInterruptedExcepNonを呼び出し側に広めるのでないならば、
interruptedExcepNonをcatchしてからインタラプテッドステータスを
復元すべき。:Thread.currentThread().interrupt()
- 12. 7-‐1-‐3
インタラプションへの応答
-‐
1
• InterruptedExcepNonの処理
1. 例外を広める(List
7-‐6)
2. インタラプテッドステータスを復元して呼び出し、
スタックの上のほうがそれを処理するようにする
• InterruptedEcepNonはもみ消してはいけない。
PrimeProducer(List
7-‐3)でもみ消しているのはスレッ
ドが終了するだけのため。
- 13. 7-‐1-‐3
インタラプションへの応答
-‐
2
• ブロックしてインタラプ書んを受け付けるメソッド
を呼び出す活動がキャンセルをサポートしてい
ない場合(List
7-‐7)
→ インタラプションのステータスを
InterruptedExcepNonのcatch後すぐに復元せず、ステー
タスを保存し、リターン直前に復元すべき
–
早くセットすると無限ループになることもあるため。
• コードがブロックしてインタラプションを受け付け
るメソッドを呼び出さない場合でも、インタラプ
テッドステータスをポーリングすることによって、
インタラプションへの応答性を持たせられます。
- 14. 7-‐1-‐4
例:実行時間の制限
-‐
1
• 無限の時間のかかる問題で「最大10分だけ答えを探
せ」と指定する。
– PrimeGenerator(List
7-‐2)は1秒経つ前に
RunNmeExcepNonを投げたら気づかれないままになる。
• 任意のRunnableを一定時間動かす試み(List
7-‐8)
– ScheDuleExecutorServiceで一定時間後にキャンセル
(taskThread.interrupt())を呼び出して止める
– 例外はNedRunを呼び出す側でcatchできる。
– この方法は反則。スレッドにインタラプションするためには
そのスレッドのインタラプションポリシーを知ってなければ、
ならないため。
• タイムアウトの前にタスクが終了したら、Returnした後で、スタート
する。
- 15. 7-‐1-‐4
例:実行時間の制限
-‐
2
• 専用スレッドの中でタスクにインタラプトする例(List
7-‐9)
– 2つの問題を解決
• aSecondPrimeOfPrimesの例外処理がcatchされなの問題
• List
7-‐8のキャンセルタスクの実行タイミングの問題
– 挙動
• NmedRunは時間制限付きのjoinをその新たに作られたスレッドで
実行。JoinでtaskThreadが終わるまで待つ
• 例外が投げられていた場合はNmedRunの呼び出したスレッドの
中で再投する
– 欠点
• 制御が現在のスレッドに戻ったのはスレッドが正常終了したのか、
joinがタイムアウトしたのか分からない。(Thread
APIの欠点)
- 17. 7-‐1-‐6
インタラプトできないブロッキン
グの扱い -‐
1
• ブロックするメソッドやブロックの仕組みのすべてがインタラプションに応答すると
はかぎらない。
• インタラプションに似た方法でストップさせることが可能な場合もありますが、その
ためにはスレッドがブロックしている理由に関する詳しい知識が必要。
• 例
– Java.ioの同期ソケットI/O
• ソケットをクローズするとread/writeでブロックしているスレッドはSocketExcepNonを投げる。
– Java.nioの同期I/O
• InterrupNbleChannel上でウェイトしているスレッドにインタラプトするとClosedByInterrptExcepNonを投
げてチャネルをクローズする。
– Selectorによる非同期I/O
• スレッドがSelector.selectでブロックしていると、wakeupがClosedSelectorExcepNonを投げてselectを途
中でリターン。
– ロックの取得
• スレッドが固有のロック(2-‐3-‐1)を持ってブロックしている時は、そのスレッドをストップするためにできる
ことはない。
• ロックを取得させて処理を進行させ、ほかの方法でスレッドの注意を引くことしかできない。
• Lockを待ちながらインタラプションに応答できるLockクラスのlockInterrupNblyメソッド
- 18. 7-‐1-‐6
インタラプトできないブロッキン
グの扱い
-‐
2
• 標準的でないキャンセルをカプセル化する
ReaderThread(List
7-‐11)
– Interruptをオーバーライドして、「標準のインタラ
プトを渡すこと」と「ソケットをクローズすること」の
両方をやらせる
• Readでブロックしていても、ブロックしてインタラプトを
受け付けるメソッドを呼び出し中でもスレッドを停止で
きる。
- 19. 7-‐1-‐7
標準的でないキャンセルを
newTaskForでカプセル化する
-‐
1
• 標準的でないキャンセルをカプセル化したテ
クニックをThreadPoolExecutorのnewTaskFor
フックで洗練させる
– ExecutorServiceにCallableを依頼するとき、submit
はタスクをキャンセルするために使えるFutureを
返します。
– Future.cancel()をオーバーライドするとタスクに対
して「ソケットを使うスレッドのキャンセルをカプセ
ル化」(List
7-‐11)するのと同等のことができる
- 20. 7-‐1-‐7
標準的でないキャンセルを
newTaskForでカプセル化する
-‐
2
• newTaskForでタスクの標準的でないキャンセルをカプセル化する(List
7-‐12)
–
CancellableTaskインタフェイス
• Callableをextendsしている
• CancellingExecutorがThreadPoolExecutorをextendsし、newTaskForをオーバーライトして
CancellableTaskに自分のFutureを作らせている。
– SocketUsingTaskアブストラクトクラス
• CancellableTaskをimplementsする
• Future.cancel()を定義してsuper.cancel()に加えソケットをクローズする。
– 挙動
• SocketUsingTaskがFutureからキャンセルされると、ソケットがクローズされ、実行中のス
レッドがインタラプトされます。
– 利点
• キャンセルに対するタスクの応答性が良くなる
• キャンセルにたいする応答性を維持できる
• ブロックしてインタラプトを受け付けるメソッドを安全に呼び出せる
• ブロックするソケットI/Oのメソッドも呼び出せる
- 22. 7-‐2-‐1
例:ログ記録サービス
-‐
1
• log
メソッドを呼び出してログメッセージをキューに入れ、そ
れを別のスレッドに処理させるクラス
• シャットダウンをサポートしないプロデューサー・コンシュー
マ型のログサービス(List
7-‐13)
– BlockingQueueでログ記録スレッドにメッセージを渡す
• (マルチ)プロデューサー:ログの呼び出し
• (シングル)コンシューマ:ログの記録
– Takeを何度も呼んでログ記録スレッドを終わらせる
• Takeはインタラプションに応答する
– 問題点
• ログに書かれていないメッセージの破棄
• logメソッドの中でブロックしていたスレッドがブロックを解かれない。
– プロデューサーとコンシューマの両方をキャンセルすることが
必要
- 23. 7-‐2-‐1
例:ログ記録サービス
-‐
2
• 「シャットダウンがリクエストされた」フラグで
メッセージの送付を禁止(List
7-‐14)
– 欠点
• 競り合い状態があるので動作が不安定
• シャットダウンの後でもメッセージをキューに入れられ
ます。
→
log()でのブロッキングが発生
- 24. 7-‐2-‐1
例:ログ記録サービス
-‐
3
• ログメッセージの送付をアトミックな操作にす
る(List
7-‐15)
– 競り合い状態をなくす
– シャットダウンのチェックをアトミックにして、シャッ
トダウンでなければカウンターをインクリメントし、
メッセージを送付する権利を予約する
- 25. 7-‐2-‐2
ExecutorServiceのシャットダウン
• 2つの終了方法は安全性と応答性のトレードオフを提
供
1. Shutdown():
穏やかなシャットダウン
2. shutdownNow():
唐突なシャットダウン
• shutdownNowが実行中のすべてのタスクのキャンセルを試みた
あと、まだスタートしていなかったタスクのリストを返す。
• 自分のスレッドの管理をExecutorServiceに委譲
– ExecutorServiceを使うログサービス(List
7-‐16)
– ExecutorServiceをカプセル化するとリンクがもう一つ増え
るので、所有のつながりがアプリケーションからサービス
へ、サービスからスレッドへと延びます。このつながりの
各メンバが、所有するサービスやスレッドのライフサイク
ルを管理する
- 26. 7-‐2-‐3
毒薬(Poison
pill)
• キューに「これをもらったら停止せよ」を意味するオブジェクトを入れておく。
– コンシューマはシャットダウンの前に自分のキューの仕事を片づけることがで
きる
– プロデューサーはPoison
pillをキューに入れた後はタスクを追加してはいけな
い
• クローラのインデックスの挙動
– List
7-‐17
IndexingServic:
POISON
Fileを定義
– List
7-‐18
IndexServiceのプロデューサスレッド:
finally
でpoison
pillをput
– List
7-‐19
IndexingServiceのコンシューマスレッド:
poison
pillなら
breakして終
了
• Poison
pill
はプロデューサー・コンシューマーの数が分かっているときだ
け使える
– 各プロデューサーが一つPoison
pillをputし、コンシューマーはN個のPoison
pillを確認した時に終了できる
- 28. 7-‐2-‐5
shutdownNowの制約
-‐
1
• スタートしたけど、完了していないタスクを見つけ
る一般的な方法はない。
– 2つの完了していない状態のタスク
1. スタートしなかったタスク
2. Executorがシャットダウンした時に進行中だったタスク
• シャットダウン時に進行中だったタスクを判断
するTrackingExecutor(List
7-‐21)
– ExecutorServiceをカプセル化してexecuteを書き換え
る。
– シャットダウン後にキャンセルされたタスクを記録
- 29. 7-‐2-‐5
shutdownNowの制約
-‐
2
• WebCrawler
(List
7-‐22):
TrackingExecutorのアプ
リ
– シャットダウン時にその状態を保存して、後でリス
タートすべき。
– クローラーがシャットダウンされると、下記のタスクの
urlを記録(stop
メソッド)
• スタートしなかったタスク
• 途中でキャンセルされたタスク
– 問題点
• 完了したタスクをキャンセルと記録する競り合い状態が発
せする。
→冪等(idempotent,
2度実行しても一度実行した結果と同じ)にし
て対処
- 31. 7-‐3-‐1
未捕捉例外ハンドラ -‐
1
• UncaughtExcepNonHandler
– Catchされていない例外でスレッドが死んだことを
検出(Thread
API)
– 未捕捉の例外で終了したときJVMはイベントを
UncaughtExcepNonHandler(List
7-‐24)に報告
– UncaughtExcepNonHandlerがない時、スタックと
レースをSystem.errにプリント
• 未捕捉の例外はアプリのQOS次第でログに
記録したりする。
- 32. 7-‐3-‐1
未捕捉例外ハンドラ
-‐
2
• プールのスレッドへのUncaughtExcepNonHandlerの設
定
– ThreadPoolExecutorのコンストラクタにThreadFactoryを渡
す。
– リカバリ処理をする時
• Runnable,
callableでタスクをラップ
• ThreadPoolExecutorのaderExecuteフックをオーバーライト
• タスクが投げた例外が未捕捉例外ハンドラまでいくの
はexecuteで依頼したタスクだけ。Submitで依頼したタ
スクでは、チェックされる例外もされない例外もすべて、
タスクのリターンステータスの一部とみなされる。
• Submitで依頼したタスクが例外で終了すると、
Future.getがExecuNonExcepNonでラップして再投する。
- 34. 7-‐4-‐1
シャットダウンフック
-‐
1
• 整然としたシャットダウン
– Shutdown
hooksを全て実行する
– RunNme.addShutdownHookで登録したスレッド
• 複数のシャットダウンフックをスタートする順序は不定
• シャットダウンフック完了 →
runFinalizersOnExitがtrueでファ
イナライザを実行
→
停止
• JVMの停止
– スレッドの停止、インタラプトもしないので、JVM停止
時に突然止まる
– 唐突なシャットダウン。シャットダウンフックも実行され
ない。
- 35. 7-‐4-‐1
シャットダウンフック
-‐
2
• シャットダウンフックはスレッドセーフであるべき。
• シャットダウンフックはサービスやアプリの後始末に使う
– 一時ファイルの削除
– OSが自動に掃除してくれない資源を掃除
• LogServiceにシャットダウンフックを登録(List
7-‐26)
– ログファイルを確実にクローズ
– シャットダウンフックは全員が平行に動く。
• アプリや他のシャットダウンフックがシャットダウンするかもしれない
サービスに依存しないようにする。
• 全てのサービスに対応する一つのシャットダウンフックを使う。
– シャットダウンフックを使わない場合も逐次でシャットダウンアク
ションを呼び出すのは有効
- 36. 7-‐4-‐2
デーモンスレッド
• 2種類のスレッド
– 正規のスレッド
– デーモンスレッド
• メインのスレッド以外はデーモンスレッド
• 2つのスレッドの違いは終了処理
– 残っているスレッドがデーモンスレッドだけならJVMの
整然としたシャットダウン。デーモンスレッドはfinally
ブロックは実行されず破棄される。
• デーモンスレッドでは、サービスのライフサイク
ルを正しく管理できない。
- 38. まとめ
• 終末処理は、設計と実装の大きな難題の一
つ。
• 強制的にキャンセルしたりスレッドを終わらせ
る仕組みがない。
– 協力的な介入(インタラプト)の仕組みを使う
• FutureTaskとExecutorフレームワークを使うと、
キャンセルできるタスクやサービスを簡単に
構築できる。