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