18
Effective Java 輪輪輪 Item 71-73 輪輪輪 輪輪輪 2014/3/5

Effective Java 輪読会 項目71-73

Embed Size (px)

Citation preview

Page 1: Effective Java 輪読会 項目71-73

Effective Java 輪読会Item 71-73

開発部 陳映融  2014/3/5

Page 2: Effective Java 輪読会 項目71-73

第 10 章 並行性

項目 66 共有された可変データへのアクセスを同期する 項目 67 過剰な同期は避ける 項目 68 スレッドよりエグゼキューターとタスクを選ぶ 項目 69 wait と notify よりコンカレンシーユーティリティを

選ぶ 項目 70 スレッド安全性を文書化する

項目 71 遅延初期化を注意して使用する 項目 72 スレッドスケジューラに依存しない 項目 73 スレッドグループを避ける

2

Page 3: Effective Java 輪読会 項目71-73

Item 71遅延初期化を注意して使用する

Page 4: Effective Java 輪読会 項目71-73

遅延初期化について

4

遅延初期化とは? フィールドの値が必要となるまで、フィールドの初期化を遅らせる行為

どこで使用する? static フィールドとインスタンスフィールドの両方に適用可能

なんのための技法? 主に最適化のために使用

アクセスコストの増加を犠牲に クラスの初期化コストやインスタンスの生成コストを減少させる

クラスとインスタンスの初期化において問題がある循環を断ち切るためにも 例: “ Java Puzzlers”, Puzzle 51: What’s the Point?

Page 5: Effective Java 輪読会 項目71-73

最適化での使用に対する最高の助言

5

「項目 55 注意して最適化する」を思い出して ...

必要でなければするな フィールドがクラスの複数インスタンスの一部でだけアクセスされる そしてフィールドの初期化にコストを要する場合は価値ある ... かもし

れない

実際のパフォーマンスを測定したうえ判断すべき アクセスコストとクラス初期化コスト・インスタンス生成コストのトレー

ドオフ 頻繁にアクセスされる場合は、かえってパフォーマンスを悪くする可能

性も

Page 6: Effective Java 輪読会 項目71-73

複数スレッドでの遅延初期化

6

複数スレッドがフィールドを共有する場合、同期の形式が重要で、そうしないと深刻なバグとなることも(項目 66 )

殆どの場合は、遅延初期化より普通の初期化が望ましい

初期化循環を断ち切るために遅延初期化を使用した場合 同期されたアクセッサーを使用

上記の2つのイデオムはフィールド宣言とアクセッサー宣言に static 修飾子を追加するだけで static フィールドに適用できる

// インスタンスフィールドの初期化:  final 修飾子使用した普通の初期化private final FieldType field = computeFieldValue();

// インスタンスフィールドの初期化: 同期されたアクセッサー内の遅延初期化private FieldType field;synchronized FieldType getField() { if (field == null) field = computeFieldValue(); return field;}

Page 7: Effective Java 輪読会 項目71-73

複数スレッドでの遅延初期化

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;}

Page 8: Effective Java 輪読会 項目71-73

複数スレッドでの遅延初期化

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;}

Page 9: Effective Java 輪読会 項目71-73

まとめ

9

殆どのフィールドは普通に初期化するべき(遅延なし)

遅延初期化を使用しなければならない場合は適切な技法を使用 初期化循環を断ち切るため

同期されたアクセッサー パフォーマンス目標を達成するため: static フィールド

遅延初期化ホルダークラスイデオム パフォーマンス目標を達成するため:インスタンスフィールド

二重チェックイデオム(一回のみ初期化) 単一チェックイデオム(複数回初期化許容)

Page 10: Effective Java 輪読会 項目71-73

Item 72スレッドスケジューラに依存しない

Page 11: Effective Java 輪読会 項目71-73

スレッドスケジューラとの付き合い方

11

スレッドスケジューラの仕事 実行可能な複数スレッドに対して、それぞれのスレッドの実行時間を決

める 時間の決め方は OS や JVM 実装によって、ポリシーが異なる可能性が

ある⇒ プログラムの正しさやパフォーマンスがスレッドスケジューラに依存すると移植できなくなる

頑強で応答性のよい移植可能なプログラムを書くための最善策 実行可能なスレッドの数がプロセッサの数より、大きくなり過ぎないよ

うに保証  ⇒ 選択肢を狭めることで、スレッドスケジューラの動きの差異を抑える

Page 12: Effective Java 輪読会 項目71-73

実行可能なスレッドの数を少なく保つ技法

12

個々のスレッドに何らか有益な処理を行わせてから、そこからさらなる処理を待たせる 有益な処理を行っていない場合は動作すべきではない

ビジーウェイトは使用しないように  ⇒ 他のスレッドの有益な処理の量を減らせてしまう

エグゼキューターフレームワークに関して言うと スレッドプールを適切大きさにする タスクを適度に小さくする 他のタスクから独立させる

タスクはあまり小さくするべきではない  ⇒ ディスパッチするオーバーヘッドによりパフォーマンスが低下する

Page 13: Effective Java 輪読会 項目71-73

Thread.yield の使用について

13

Thread.yield メソッド 現在使用中のプロセッサを譲ってもいい、という意思表示 スレッドスケジューラへのヒントだけなので、無視されても文句言えない  ⇒ テスト可能なセマンティックスを持っていない

実行時間が得られなくて殆ど動かないプログラムに直面した場合 Thread.yield の呼び出しを入れてプログラムを「修理」する?

使用中の JVM 実装では動くかも知れないが 他の JVM 実装ではどうなるかわからない

アプリケーションを再構築して、並行して実行可能スレッド数を減らすべき

並行性検査のために使用しないこと 代わりに Thread.sleep(1) を使用すること すぐに戻ってくる可能性のある Thread.sleep(0) は使わないように

Page 14: Effective Java 輪読会 項目71-73

まとめ

14

アプリケーションの正しさに関してスレッドスケジューラに依存してはだめ 依存性のあるアプリケーションは頑健でもない、移植可能でもない

スケジューラに対するヒントとなる機構の Thread.yield やスレッドの優先順位に依存してもだめ 動作しているプログラムの品質を改善するのにスレッドの優先順位を控

えめに使用してもよい 殆ど動作しないプログラムの「修理」に使用するべきではない

Page 15: Effective Java 輪読会 項目71-73

Item 73スレッドグループを避ける

Page 16: Effective Java 輪読会 項目71-73

スレッドグループって?

16

スレッドシステムが提供している基本的な抽象概念 スレッドの集合を表す (以下省略)

セキュリティ目的のためにアプレットを隔離する仕組みとして考案された 役割を果たすことなかった 重要性も小さくなった(セキュリティモデルの標準書が言及しないほ

ど)

Page 17: Effective Java 輪読会 項目71-73

スレッドグループが使用されない理由

17

たいして機能を提供していない Thread の基本操作を一度に多くのスレッドに適用することを可能にす

る いくつか推奨されない操作や、めったに使わない操作 ...

ThreadGroup API はスレッド安全性の観点から貧弱 enumerate :配列に入りきらないスレッドグループはそのまま無視される activeCount :アクティブなスレッドの数の「推定値」を返す activeGroupCount :アクティブなスレッドグループの数の「推定値」を返す

ThreadGroup API だけが提供している機能はもう存在しない スレッドがスローした例外がキャッチされない場合の制御手段

リリース 1.5 より前は ThreadGroup.uncaughtException だけ リリース 1.5 以降は Thread.setUncaughtExceptionHandler

Page 18: Effective Java 輪読会 項目71-73

まとめ

18

スレッドグループは有用な機能を提供していないし、提供している機能の殆どに欠陥がある

スレッドグループを成功しなかった実験とみなして、その存在を無視してもよい

スレッドを論理的なグループを扱うクラスを設計するなら、おそらく、スレッドプールエグゼキューターを使用すべき

要するに、スレッドグループをなかったことにしよう