Upload
appresso-engineering-team
View
155
Download
0
Embed Size (px)
Citation preview
Effective Java 輪読会Item 71-73
開発部 陳映融 2014/3/5
第 10 章 並行性
項目 66 共有された可変データへのアクセスを同期する 項目 67 過剰な同期は避ける 項目 68 スレッドよりエグゼキューターとタスクを選ぶ 項目 69 wait と notify よりコンカレンシーユーティリティを
選ぶ 項目 70 スレッド安全性を文書化する
項目 71 遅延初期化を注意して使用する 項目 72 スレッドスケジューラに依存しない 項目 73 スレッドグループを避ける
2
Item 71遅延初期化を注意して使用する
遅延初期化について
4
遅延初期化とは? フィールドの値が必要となるまで、フィールドの初期化を遅らせる行為
どこで使用する? static フィールドとインスタンスフィールドの両方に適用可能
なんのための技法? 主に最適化のために使用
アクセスコストの増加を犠牲に クラスの初期化コストやインスタンスの生成コストを減少させる
クラスとインスタンスの初期化において問題がある循環を断ち切るためにも 例: “ Java Puzzlers”, Puzzle 51: What’s the Point?
最適化での使用に対する最高の助言
5
「項目 55 注意して最適化する」を思い出して ...
必要でなければするな フィールドがクラスの複数インスタンスの一部でだけアクセスされる そしてフィールドの初期化にコストを要する場合は価値ある ... かもし
れない
実際のパフォーマンスを測定したうえ判断すべき アクセスコストとクラス初期化コスト・インスタンス生成コストのトレー
ドオフ 頻繁にアクセスされる場合は、かえってパフォーマンスを悪くする可能
性も
複数スレッドでの遅延初期化
6
複数スレッドがフィールドを共有する場合、同期の形式が重要で、そうしないと深刻なバグとなることも(項目 66 )
殆どの場合は、遅延初期化より普通の初期化が望ましい
初期化循環を断ち切るために遅延初期化を使用した場合 同期されたアクセッサーを使用
上記の2つのイデオムはフィールド宣言とアクセッサー宣言に static 修飾子を追加するだけで static フィールドに適用できる
// インスタンスフィールドの初期化: final 修飾子使用した普通の初期化private final FieldType field = computeFieldValue();
// インスタンスフィールドの初期化: 同期されたアクセッサー内の遅延初期化private FieldType field;synchronized FieldType getField() { if (field == null) field = computeFieldValue(); return field;}
複数スレッドでの遅延初期化
7
パフォーマンスのため static フィールドに遅延初期化を適用する場合 遅延初期化(オンデマンド初期化)ホルダークラスイデオムを使用
パフォーマンスのためインスタンスフィールドに遅延初期化を適用する場合 二重チェックイデオムを使用
// static フィールドに対する遅延初期化ホルダークラスイデオムprivate static class FieldHolder { static final FieldType field = computeFieldValue();}static FieldType getField() { // 同期が不要なのでアクセスコストが実質的に増えない! return FieldHolder.field; // 呼び出された時に初めて FieldHolder.field 初期化}
// インスタンスフィールドに対する遅延初期化のための二重チェックイデオムprivate volatile FieldType field;FieldType getField() { FieldType result = field; // result が field が初期化済みの場合は一回しか読み込まれないことを保証 if (result == null) { // 1 回目検査(ロックなし) synchronized (this) { result = field; if (result == null) // 2 回目検査(ロックあり) field = result = computeFieldValue(); } } return result;}
複数スレッドでの遅延初期化
8
複数回の初期化を許容できるインスタンスフィールドの場合 二重チェックイデオムの変形 : 単一チェックイデオム
すべてのスレッドでフィールド値( long/double 以外の基本型)が再計算されても気にしない場合 二重チェックイデオムの変形 : きわどい単一チェックイデオム
インスタンスフィールドの volatile 修飾子を取り除く long/double がダメな理由は、変数の write 処理はアトミックでないため
[JLS17.7] 非標準技法であって普段用ではない
が、 String のハッシュコードをキャッシュするため使用されている
// 単一チェックイデオムprivate volatile FieldType field;FieldType getField() { FieldType result = field; if (result == null) // 2 回目のチェックがなくなり同期しなくなったため、複数回初期化されるかも field = result = computeFieldValue(); return result;}
まとめ
9
殆どのフィールドは普通に初期化するべき(遅延なし)
遅延初期化を使用しなければならない場合は適切な技法を使用 初期化循環を断ち切るため
同期されたアクセッサー パフォーマンス目標を達成するため: static フィールド
遅延初期化ホルダークラスイデオム パフォーマンス目標を達成するため:インスタンスフィールド
二重チェックイデオム(一回のみ初期化) 単一チェックイデオム(複数回初期化許容)
Item 72スレッドスケジューラに依存しない
スレッドスケジューラとの付き合い方
11
スレッドスケジューラの仕事 実行可能な複数スレッドに対して、それぞれのスレッドの実行時間を決
める 時間の決め方は OS や JVM 実装によって、ポリシーが異なる可能性が
ある⇒ プログラムの正しさやパフォーマンスがスレッドスケジューラに依存すると移植できなくなる
頑強で応答性のよい移植可能なプログラムを書くための最善策 実行可能なスレッドの数がプロセッサの数より、大きくなり過ぎないよ
うに保証 ⇒ 選択肢を狭めることで、スレッドスケジューラの動きの差異を抑える
実行可能なスレッドの数を少なく保つ技法
12
個々のスレッドに何らか有益な処理を行わせてから、そこからさらなる処理を待たせる 有益な処理を行っていない場合は動作すべきではない
ビジーウェイトは使用しないように ⇒ 他のスレッドの有益な処理の量を減らせてしまう
エグゼキューターフレームワークに関して言うと スレッドプールを適切大きさにする タスクを適度に小さくする 他のタスクから独立させる
タスクはあまり小さくするべきではない ⇒ ディスパッチするオーバーヘッドによりパフォーマンスが低下する
Thread.yield の使用について
13
Thread.yield メソッド 現在使用中のプロセッサを譲ってもいい、という意思表示 スレッドスケジューラへのヒントだけなので、無視されても文句言えない ⇒ テスト可能なセマンティックスを持っていない
実行時間が得られなくて殆ど動かないプログラムに直面した場合 Thread.yield の呼び出しを入れてプログラムを「修理」する?
使用中の JVM 実装では動くかも知れないが 他の JVM 実装ではどうなるかわからない
アプリケーションを再構築して、並行して実行可能スレッド数を減らすべき
並行性検査のために使用しないこと 代わりに Thread.sleep(1) を使用すること すぐに戻ってくる可能性のある Thread.sleep(0) は使わないように
まとめ
14
アプリケーションの正しさに関してスレッドスケジューラに依存してはだめ 依存性のあるアプリケーションは頑健でもない、移植可能でもない
スケジューラに対するヒントとなる機構の Thread.yield やスレッドの優先順位に依存してもだめ 動作しているプログラムの品質を改善するのにスレッドの優先順位を控
えめに使用してもよい 殆ど動作しないプログラムの「修理」に使用するべきではない
Item 73スレッドグループを避ける
スレッドグループって?
16
スレッドシステムが提供している基本的な抽象概念 スレッドの集合を表す (以下省略)
セキュリティ目的のためにアプレットを隔離する仕組みとして考案された 役割を果たすことなかった 重要性も小さくなった(セキュリティモデルの標準書が言及しないほ
ど)
スレッドグループが使用されない理由
17
たいして機能を提供していない Thread の基本操作を一度に多くのスレッドに適用することを可能にす
る いくつか推奨されない操作や、めったに使わない操作 ...
ThreadGroup API はスレッド安全性の観点から貧弱 enumerate :配列に入りきらないスレッドグループはそのまま無視される activeCount :アクティブなスレッドの数の「推定値」を返す activeGroupCount :アクティブなスレッドグループの数の「推定値」を返す
ThreadGroup API だけが提供している機能はもう存在しない スレッドがスローした例外がキャッチされない場合の制御手段
リリース 1.5 より前は ThreadGroup.uncaughtException だけ リリース 1.5 以降は Thread.setUncaughtExceptionHandler
まとめ
18
スレッドグループは有用な機能を提供していないし、提供している機能の殆どに欠陥がある
スレッドグループを成功しなかった実験とみなして、その存在を無視してもよい
スレッドを論理的なグループを扱うクラスを設計するなら、おそらく、スレッドプールエグゼキューターを使用すべき
要するに、スレッドグループをなかったことにしよう