Your SlideShare is downloading. ×
20130611 java concurrencyinpracticech7
Upcoming SlideShare
Loading in...5
×

Thanks for flagging this SlideShare!

Oops! An error has occurred.

×
Saving this for later? Get the SlideShare app to save on your phone or tablet. Read anywhere, anytime – even offline.
Text the download link to your phone
Standard text messaging rates apply

20130611 java concurrencyinpracticech7

697
views

Published on


0 Comments
0 Likes
Statistics
Notes
  • Be the first to comment

  • Be the first to like this

No Downloads
Views
Total Views
697
On Slideshare
0
From Embeds
0
Number of Embeds
1
Actions
Shares
0
Downloads
2
Comments
0
Likes
0
Embeds 0
No embeds

Report content
Flagged as inappropriate Flag as inappropriate
Flag as inappropriate

Select your reason for flagging this presentation as inappropriate.

Cancel
No notes for slide

Transcript

  • 1. Java並列処理プログラミング  第7章  キャンセルとシャットダウン 2013/6/11  遠山敏章
  • 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:  キャンセルがリクエストされたことを検出したら終了する  
  • 7. 7-­‐1-­‐1  インタラプション •  タスクがキャンセルフラグをチェックせず永久に終わらないケース(List  7-­‐3)  – BlockingQueueのputでブロックされたスレッドはcancelledフラグをチェックできない  •  ブロックするメソッドはインタラプションをサポート(第5章)  •  キャンセル以外の目的にインタラプションを使うとプログラムの安定性を損ない、脆弱になる。
  • 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の欠点)  
  • 16. 7-­‐1-­‐5  Futureからキャンセルする •  Futureを使って、タスクをキャンセルする(List  7-­‐10)  – taskExec.submitでfutureを返す  – task.cancel(false)で実行中ならインタラプトする  – タスクがキャンセルより前に例外を投げたら再投する  
  • 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のメソッドも呼び出せる  
  • 21. 7-­‐2  スレッドを使っているサービスを停止する •  シャットダウンするときはサービスが所有するスレッドも終わる必要がある。  •  カプセル化を壊さないためにはスレッドのオーナーではないコードがスレッドを操作してはいけない。(インタラプト、プライオリティの変更)  –  スレッドプールはそのワーカースレッドのオーナーなので、スレッドがインタラプトされたら、スレッドプールが面倒を見るべき。  •  スレッドを所有するサービスはスレッドをシャットダウンするライフサイクルメソッドを持つべき。  –  ExecutorService    •  shutdon(),  shutdownNow()
  • 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を確認した時に終了できる  
  • 27. 7-­‐2-­‐4  例:1回かぎりの実行サービス •  タスクのバッチ処理ですべてのタスクを処理するまでリターンしないメソッド  •  PrivateなExecutorをつかってライフサイクルを簡単に管理できる。  – 新着メールを複数のホストの上で並列にチェックするcheckMailメソッド  (List  7-­‐20)  •  Executorの寿命はメソッドの寿命とイコール
  • 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度実行しても一度実行した結果と同じ)にして対処
  • 30. 7-­‐3  スレッドの異常終了を扱う •  アプリケーションからスレッドが漏れることを検出し、防ぐ方法  •  スレッドが途中で死ぬ原因  –  RunNmeExcepNon  –  GUIアプリのイベントディスパッチスレッド(EDT)の喪失  •  フリーズする  •  スレッドプール内のワーカースレッドを構造化する例(List  7-­‐23)  –  タスクが例外投げたらスレッドを殺す。  –  不良なタスクがその後のタスクの実行を妨げないようにする
  • 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でラップして再投する。  
  • 33. 7-­‐4  JVMのシャットダウン 1.  整然(orderly)としたシャットダウン  –  最後の「正規の」スレッドが終了  –  System.exit  –  プラットフォーム固有の終了(SIGINT,  Ctr-­‐C)  2.  唐突(abrupt)なシャットダウン  –  RunNme.halt  –  OSからJVMのプロセスをkill(SIGKILL)
  • 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ブロックは実行されず破棄される。  •  デーモンスレッドでは、サービスのライフサイクルを正しく管理できない。  
  • 37. 7-­‐4-­‐3  ファイナライザ •  ファイルやソケットのハンドルなど一部の資源あh、要らなくなったらOSに明示的に返す必要がある  •  ファイナライザのあるクラスを使うことや書くことを避ける。  – 理由  •  アクセスするステートの同期化が必要  •  Finalizeメソッドを独自に定義しているオブジェクトの実行性能の足を大きく引っ張る  •  正しく書くのは大変難しい  
  • 38. まとめ •  終末処理は、設計と実装の大きな難題の一つ。  •  強制的にキャンセルしたりスレッドを終わらせる仕組みがない。  – 協力的な介入(インタラプト)の仕組みを使う  •  FutureTaskとExecutorフレームワークを使うと、キャンセルできるタスクやサービスを簡単に構築できる。  
  • 39. Q&A