Jの衝動書き日記

さらりーまんSEの日記でございます。

 Java並行処理プログラミング 勉強会まとめ その2

スレッドに関する超良書、Java並行処理プログラミングで学んだことをまとめたものである。とりあえずはここまで。スレッドセーフの概念に関してはこれで最低限網羅できたはずだ。一応全部読破はしているけど、ポイントをまとめていると、理解出来ていない箇所がまるわかりになるので、そういう意味でもブログに書く価値はある。なんてことを考えながら書いたメモである。

可視性について

  • 何故読み込み時にも同期化が必要なのか?

他のスレッドが変更中だと、不完全な状態で値を読み込んでしまう可能性があるから。不完全な状態とは、例えばロックで保護されているステートが複数ある場合、それらのステートすべての変更が終わる前に、別のスレッドが値を読んでしまう時に起こる。

  • Java特有の問題

複数スレッドで共有化されるデータで、それが変更可能である場合同期化が必要。
何故か?

    • 同期化をしないと、書き込んだ順とは逆の順序で別スレッドから見えることがある

コンパイラが処理の順番を入れ替えることがあるから。その処理で同期化を行っている場合は起きない(禁止されている)。

   例) 
    ソース上                 実際
    書き込み a              書き込み b
    書き込み b              書き込み a 
    • 同期化をしないと、変更した結果が他のスレッドでは見えないこともある

共有変数の実際に見える値の古さは一定ではない(陳腐化したデータ)。
例えば、共有変数の値を、各スレッドがキャッシュしている場合、共有変数を変更したからといって、その結果は各スレッドには伝わらない。故に古い値を見るということが起こる。同期化をしている場合、値のキャッシュを禁止するので、変更された共有値は確実に見ることが出来る。

    • 同期化を行うということは、共有値の最新値を確実に見ること、可視性を保証する。

故に読み込みの時も同期化が必要。
 

  • ロックによる同期化が保証するもの
    • 可視性
    • アトミック性

アトミック性は、操作A、Bについて以下が成り立つような状態をいう。
Aをこれから実行しようとするスレッドから見て、その時Bを実行する別のスレッドが必ずBの実行を完全に終えているか、またはまったくBに未着手。
  

  • 揮発性変数(volatile variables)

volatile宣言した変数は、可視性を保証する(アトミック性は保証しない)

    • 処理順序の入れ替えが発生しない
    • レジスタにキャッシュされない
    • ロックはしないのでスレッドをブロックしない

用途としては、スレッドのシャットダウンに用いる変数(フラグ)などに用いる。アトミック性は保証されないので、現在の値に依存した変更操作は避ける(++操作等)。
 

オブジェクトの公開と逸脱

  • オブジェクトの逸脱とは?

公開すべきでないオブジェクトが公開されてしまうことをいう。

  • 発生する状況
    • オブジェクトのステート状態が不完全な状態で公開する
    • オブジェクトの初期化が終わる前に別スレッドがアクセスしてしまう(同期化をしていない場合)
    • 内部的な可変ステートを公開してしまう
 private Map map;
 public Map getMap { return this.map;}
  • this参照の逸脱

コンストラクタの中でthis参照を渡してはいけない。
発生する状況は?

何故this参照を逸脱させては駄目なのか?

    • 不完全な状態でアクセス可能になるから

 →初期化が終わってない。ずっと終わらないかも(可視性の問題)。

  • 安全にオブジェクトを公開する

オブジェクトの参照とオブジェクトのステートの両方が同時に他のスレッドから可視にする。初期化が完全に終わっている状態で公開
   

  • 安全にオブジェクトを公開するための定石
    • オブジェクトの参照をstaticイニシャライザで初期化する
    • オブジェクトの参照をvolatileフィールド、またはAtomicReferenceに保存する
    • オブジェクトの参照を正しく構築されたオブジェクトのfinalフィールドに保存する
    • オブジェクトの参照をロックによって正しくガードされたフィールドに保存する

正しい構築とは、コンストラクションの間にthis参照が逸脱していない状況をいう。

スレッドセーフとなるクラスの設計指針

  • クラス設計の順序
    • オブジェクトのステートを構成する変数を検討
    • ステート変数の値などを制約する不変項を定義
    • ステートへの並行アクセスを管理するためのポリシーを確立
  • 不変項の定義

ステートの値の範囲で、何が無効で何が有効なのかを考え定義する。
操作の結果、無効なステート遷移とならないか → 事後条件

 例)
  カウンタの場合 :負数不可 → 不変項 
  値が18→19に変わるのが正しいステート遷移 → 事後条件 

  

複数のステート間で不変項が存在する場合、その単位でカプセル化するのがよい。
→ ステートへのアクセスがメソッドに拘束され、常に正しいロックが取得された状態でアクセス出来るから
ただし、逸脱させないように注意する
 → listの要素を返す ◯ listを丸ごと返す ×
   → カプセル化しているのに,listへのアクセスが保護出来ない

参考文献

Java並行処理プログラミング ―その「基盤」と「最新API」を究める―

Java並行処理プログラミング ―その「基盤」と「最新API」を究める―