Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

Effective Java 輪読会 項目66-68

860 views

Published on

Published in: Technology
  • Login to see the comments

  • Be the first to like this

Effective Java 輪読会 項目66-68

  1. 1. Effective Java 輪読会第8回 (項目66~68) 2014/3/5 開発部野口
  2. 2. 項目66 共有された可変データへのアクセスを同期す る
  3. 3. synchronized  以下の2 つを保証する  スレッドが、不整合な状態のオブジェクトを見な いこと  スレッドから、同じロックで保護されていた以前 のすべての変更の結果が見えること
  4. 4. アトミックな型  long 型とdouble 型以外の変数の読み書きがア トミックであることは、言語仕様によって保 証されている  では、パフォーマンスのため、アトミックなデー タへの読み書きでは同期を避けるべき?  そうではない
  5. 5. メモリモデル  言語仕様では、フィールドを読み出すときに スレッドがランダムな値を読み出さないこと を保証している  が、1 つのスレッドが書き込んだ値が他のス レッドからも見えることは保証していない  ので、アトミックなデータに対しても同期は 有用
  6. 6. 不完全な同期の例 public class StopThread { private static boolean stopRequested; public static void main(String[] args) throws InterruptedException { Thread backgroundThread = new Thread(new Runnable() { public void run() { int i = 0; while (!stopRequested) i++; }}); backgroundThread.start(); TimeUnit.SECONDS.sleep(1); stopRequested = true; // この変更がbackgroundThread には伝わらな い! // ので、プログラムが終了しない(活性エラー) }}
  7. 7. 巻き上げ  HotSpot Server VM は、以下のような変更を行う(この 変更は許容されている) ↓ while (!stopRequested) i++; if (!stopRequested) while (true) // !!! i++;
  8. 8. 不完全な同期の修正案 public class StopThread { private static boolean stopRequested; public static synchronized void requestStop() { stopRequested = true; } public static synchronized boolean stopRequested() { return stopRequested; } public static void main(String[] args) throws InterruptedException { Thread backgroundThread = new Thread(new Runnable() { public void run() { int i = 0; while (!stopRequested()) i++; }}); backgroundThread.start(); TimeUnit.SECONDS.sleep(1); requestStop(); // 変更がbackgroundThread にも反映される // ので、プログラムは1 秒で終了する }}
  9. 9. 修正案の注意点  読み込み操作(stopRequested)と書き込み操 作(requestStop)の両方が同期されている必 要がある
  10. 10. volatile による修正案 public class StopThread { private static volatile boolean stopRequested; public static void main(String[] args) throws InterruptedException { Thread backgroundThread = new Thread(new Runnable() { public void run() { int i = 0; while (!stopRequested) i++; } }); backgroundThread.start(); TimeUnit.SECONDS.sleep(1); stopRequested = true; // 反映される }
  11. 11. volatile の失敗例 // 不完全- 同期が必要! private static volatile int nextSerialNumber = 0; public static int generateSerialNumber() { return nextSerialNumber++; // この操作はアトミックではない! // ので、タイミング次第では、複数のスレッドで // 同じシリアルナンバーを得てしまう(安全性エ ラー) }
  12. 12. AtomicLong public static synchronized long generateSerialNumber() { if (nextSerialNumber == Long.MAX_VALUE) throw new なんとかException(); return nextSerialNumber++; ↑同等!(かつ、後者の方が速い可能性が高い)↓ } private static final AtomicLong nextSerialNum = new AtomicLong(); public static long generateSerialNumber() { return nextSerialNum.getAndIncrement(); }
  13. 13. 可変データを共有しない  最善!  その際は、文書化しよう(方針が維持される ように)  フレームワークやライブラリを深く理解しよ う(内部でスレッドが用いられているかもし れない)
  14. 14. 事実上不変  「少しの間スレッドがデータオブジェクトを 変更してから、オブジェクト参照を共有する 処理だけを同期する」  「オブジェクトが再び変更されない限り、他のス レッドはさらに同期することなくオブジェクトを 読み出すことができる」 // (”Java Concurrency in Practice” より引用) public Map<String, Date> lastLogin = Collections.synchronizedMap(new HashMap<String, Date>());
  15. 15. 安全な公開  フィールドをstatic にして、クラス初期化に よろしくお願いする  フィールドをvolatile にする  フィールドをfinal にする  フィールドをロックで保護する  コンカレントコレクションを用いる
  16. 16. まとめ  複数のスレッドが可変データを共有する場合、そ のデータを読み書きするスレッドは同期を行う  同期なしでは、あるスレッドの変更が他のスレッドか ら見えることは保証されない  同期しないことへのペナルティ:活性エラーと安 全性エラー  デバッグ困難!  タイミング依存、JVM 依存  スレッド間通信だけが必要な場合は、volatile も ある  ただし、正しく使用するのは難しい  詳しくは『Java Concurrency in Practice』を読も う(邦訳もあるよ)
  17. 17. 項目67 過剰な同期は避ける
  18. 18. 同期されたメソッドやブロック内 で、制御をクライアントに譲らな い  オーバーライドされるように設計されている メソッドや関数オブジェクトを、同期された 領域内で呼び出さない  <異質>なメソッドだから  メソッドが何をするかわからないため、例外、デッド ロック、データ破壊の可能性がある
  19. 19. 異質なメソッド呼び出し (例外編)  ObservableSet(pp.256-257)と、 SetObserver(pp.257)  SetObserver#added で ObservableSet#removeObserver を呼び出すと、 ConcurrentModificationException がスローされ る!(pp.257-258)  リストをイテレート中に、そのリストから要素を削除 しようとしているから
  20. 20. 異質なメソッド呼び出し (デッドロック編)  再びObservableSet(pp.256-257)と、 SetObserver(pp.257)  SetObserver#added で、ExecutorService 経由で ObservableSet#removeObserver を呼び出すと、 デッドロックが発生する!(pp.258)  ObservableSet.observers のロックは、既にメインス レッドによって獲得されているから
  21. 21. 異質なメソッド呼び出し (地獄編)  もし、 1. 同期された領域により保護されている不変式が一時 的に不正になっている間に、 2. 同期された領域内から異質なメソッドを呼び出した ら、 3. その異質なメソッドは首尾よくロックを獲得し(再 帰的ロック)、 4. オブジェクトの内部状態をこっそり不正にしてしま う!(かもしれない)  ロックが用をなしていない  再帰的ロックは、活性エラーを安全性エラーに変える 可能性がある
  22. 22. 異質なメソッド呼び出しの回避 (オープンコール) private void notifyElementAdded(E element) { List<SetObserver<E>> snapshot = null; synchronized(observers) { snapshot = new ArrayList<SetObserver<E>>(observers); } for (SetObserver<E> observer : snapshot) observer.added(this, element); }  SetObserver#added の処理時間が長い場合、並行性 を増大させるメリットも期待できる
  23. 23. 異質なメソッド呼び出しの回避 (CopyOnWriteArrayList) private final List<SetObserver<E>> observers = new CopyOnWriteArrayList<SetObserver<E>>(); public void addObserver(SetObserver<E> observer) { observers.add(observer); } public void notifyElementAdded(E element) { observer.added(this, element); } private void addObserver(SetObserver<E> observer) { for (SetObserver<E> observer : observers) observers.add(observer); }
  24. 24. 同期のコスト  ロックに費やされるCPU 時間  よりも、並行処理を行う機会損失  および、すべてのコアがメモリの一貫した ビューを持つことを保証する必要性から生じ る遅延
  25. 25. 内部同期と外部同期  クラスが並行して使用されるのであれば、可変クラス をスレッドセーフにすべき  static フィールドへのアクセスは、必ず内部同期が必 要  関係のないクライアント同士が、同じルールで同期できる とは限らないから  StringBuffer とStringBuilder  StringBuffer は、ほとんどの場合に単一スレッドから使用 されるのに、内部的に同期を行っている  リリース1.5 で、同期されていないStringBuilder によっ て置き換えられた  教訓:必要性が疑わしい場合には、内部的な同期を行わず、 スレッドセーフでないことを文書化する
  26. 26. まとめ  デッドロックやデータ破壊を回避するため、同期 された領域から異質なメソッドを呼び出さない (決して!)  より一般的には:同期された領域内で行う処理の量を 制限する  可変クラスを設計する場合は、内部同期を検討す る  並行性のため、過剰には同期せず、内部同期の採用が 妥当でない場合はその旨をドキュメント化する  詳しくは『Java Concurrency in Practice』を読も う(邦訳もあるよ)
  27. 27. 項目68 スレッドよりエグゼキューターとタスク を選ぶ
  28. 28. エグゼキューターフレームワーク に 親しもう // ワークキューを作成 ExecutorService executor = Executors.newSingleThreadExecutor(); // runnable を実行のために発行 executor.execute(runnable); // 終了を指示 executor.shutdown();  楽ちん便利
  29. 29. エグゼキューターサービスの 多彩な機能  特定のタスクが完了するのを待つ  タスクの集まりの中のどれかのタスクや、す べてのタスクが完了するのを待つ  エグゼキューターサービスがきちんと完了す るのを待つ  タスクが完了するごとに、1 つずつタスクの 結果を取り出す  スレッドプールを作成する
  30. 30. エグゼキューターサービスの選択  小さなプログラムや、軽い負荷のサーバーな ら:  Executors.newCachedThreadPool  設定不要、一般に「正しいことを行う」  高負荷の製品サーバーなら:  Executors.newFixedThreadPool  固定数のスレッドを持つプールを提供する  ThreadPoolExecutor を直接使用  最大限の制御が可能
  31. 31. Thread を直接使うのはもうやめよ う  Thread  処理の単位と、処理を実行するための機構の両方  Runnable / Callable / エグゼキューターサービ ス  <タスク>(処理の単位)とその実行機構を、そ れぞれ適切に抽象化する
  32. 32. Timer を直接使うのももうやめよ う  Timer  タスク実行のために単一スレッドしか使用してい ない  タイミングの精度に不安あり  例外がスローされると、動作しなくなる  ScheduledThreadPoolExecutor  複数スレッドをサポート  チェックされない例外をスローする例外からも回 復
  33. 33. まとめ  詳しくは『Java Concurrency in Practice』を 読もう(邦訳もあるよ)

×