デザインパターン入門 マルチスレッド編まとめ
がーっと読んだ。実際に使うときの思い出しトリガーになるようにメモしておく。
マルチスレッドプログラムの評価基準
- 安全性 オブジェクトを壊さないこと
- スレッドセーフなクラス
- 生存性 必要な処理が行われること
- 安全性を重視しただけでは生存性を下げてしまう場合がある。例えばデッドロック。
- 再利用性 クラスを再利用できること
- スレッドの排他制御の仕組みや方針をうまくクラスの中に隠蔽すれば、再利用性の高いプログラムになる。
- パフォーマンス 処理を高速・大量に行えること
安全性と生存性を守るのは必須。その上で、いかにして再利用性とパフォーマンスを上げるかが重要。
Single Thread Execution 「この橋を渡れるのは、たった一人」
- 複数のスレッドがインスタンスを共有する場合の基本パターン
- クリティカルセッション(インスタンスが不安定な状態になる範囲)をまず定める
- クリティカルセッションは1つのスレッドだけが実行するようにガードする
- Javaではsynchronizedを使って実現する
- インスタンスの状態が変化しないのであれば、Immutableパターンを使う。(スループット向上)
- インスタンスの状態を参照するスレッドと変更するスレッドが分かれているときは、Read-Write Lockパターンを使う。(スループット向上)
Immutable 「壊したくとも、壊せない」
Guarded Suspension 「用意できるまで、待っててね」
- これも頻出パターン
- インスタンスが適切な状態になるまで、ガード条件を使ってスレッドを待たせる。
- ガード条件が変化したことをスレッドの通知(notifyAll)
- ガード条件が満たされないなら待たずに帰る場合は、Balkingパターンを使う。
Balking 「必要なかったら、やめちゃおう」
- Guarded Suspensionパターンで、ガード条件が満たされない場合に待たずに帰る
Producer-Consumer 「わたしが作り、あなたが使う」
Read-Write Lock 「みんなで読んでもいいけれど、読んでる間は書いちゃダメ」
Thread-Per-Message 「この仕事やっといてね」
- スレッド(Client)がインスタンス(Host)のメソッドを呼び出している場合、メソッド処理が終了するまでHostから制御が戻ってこない。(応答性ダウン)
- Hostが処理用のスレッドを新たに起動し、処理を任せる。Clientにはすぐに応答する。(メソッドの起動とメソッドの実行の分離)
- 処理結果を戻したい場合は、Futureパターンを使う。
- スレッドを使いまわしたい場合は、Worker Threadパターンを使う。
Worker Thread 「仕事が来るまで待ち、仕事が来たら働く」
Future 「引換券をお先にどうぞ」
Two-Phase Termination 「後片付けしてから、おやすみなさい」
- 動作しているスレッドを安全に終了させたいときに利用できるパターン
- 終了処理を行うタイミングは終了するスレッド本人に判断させる
- スレッドを終了させるために「終了要求」用のメソッドを用意
- 終了要求を受けたスレッドは、安全に終了処理を実行できるタイミングで終了処理を開始する
Thread-Specific Storage 「スレッドごとのコインロッカー」
- シングルスレッドの環境で動作することを想定しているオブジェクトを、マルチスレッド環境で利用したいときに使うパターン。
- スレッドごとにデータを格納したい場合に使うパターン
- JavaではThreadLocalクラスで実現できる
Active Object 「非同期メッセージを受け取る、能動的なオブジェクト」
- シングルスレッドで動作することを前提として設計されているServant役に皮をかぶせて、マルチスレッドのClient役から利用できるようにするパターン。
- Servantを呼び出すScheduler役のスレッドを導入。Workerスレッドが1個のWorker Threadパターン。これで複数のClientが処理できる。
- Clientからの要求はProxyへのメソッド呼び出しとして実現。Proxyで要求を1つのオブジェクトに変換し、Producer-Consumerパターンを使ってSchedulerに渡す。(Clientへの応答性の確保)
- 実行すべき要求の選出はSchedulerが行う。
- 実行結果はFutureパターンを使ってClientへ返す。
【付録】Javaのメモリモデル
- Javaのメモリモデルには、「メインメモリ」と「ワーキングメモリ」がある。
- メインメモリはインスタンスが存在する領域。個々のスレッドはメインメモリには直接アクセスできない。
- ワーキングメモリは個々のスレッドが持っている作業領域。必要に応じて、メインメモリからワーキングメモリ(またはその逆)にコピーが行われる。
- 各スレッドは常にメインメモリにある最新の状態を参照できるとは限らない。(仕様上)
- synchronizedの2つの働き
- スレッドの同期
- メモリの同期。ワーキングメモリとメインメモリの内容の同期が取られる。(ただしsynchonizedに入るときのみ)
- volatileの2つの働き
- メモリの同期
- long、doubleの代入をアトミックに行う。