理系学生日記

おまえはいつまで学生気分なのか

忍者TOOLS

並行

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

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

ロックと可視性

ロックの機能は相互排他のためだけではない。

読む側のスレッドが書く側のスレッドが書いた値を正しく読める保証は無い。可視性を複数メソッドに渡って保証するためには、必ず同期化を行う必要がある。また、コンパイラによる reorder (アウトオブオーダー実行)の結果、代入順序がプログラム上とは変わる可能性もある。同期化の不十分な並行プログラムの振る舞いを人間が判断することは、ほとんど不可能である。正しい値の読み書きを保証するためには、セッターだけでなくゲッターも同期化しておかなければならない。

同期化には別の面もあり、(JMMが規定する)メモリの可視性に関する一定の規則も強制するのです。同期化は、同期化ブロックから出るときにはキャッシュがクリアされ、同期化ブロックに入るときにはキャッシュが無効化されることを保証するのです。こうすることで、あるモニターに保護された同期化ブロック期間中に一つのスレッドが書く値は、同じモニターに保護された同期化ブロックを実行している他のどのスレッドからも見えるのです。同期化はまた、コンパイラーが命令を同期化ブロック内から外へ移動することはない、ということも保証します(同期化ブロック外から内部への命令の移動は、場合によってはできますが)。

Java 1.5(バージョン5とも言う)は新たなメモリモデルを採用しており、キーワードvolatileである種のハードウェアによる命令並べ替えとコンパイラによる並べ替えを防ぐことを保証している。

volatile は synchronized よりも弱い同期化の仕組みである。synchronized は可視性とアドミック性を保証するが、volatile は可視性のみを保証する。クラスのフィールドが volatile で宣言されていると、コンパイラとランタイムは reorder を抑制し、リードは常にあるスレッドが書き込んだ最新の値を返却する。volatile によ可視性効果の及ぶ範囲は volatile と宣言された変数のみに留まらないが、それに頼った可視性制御はプログラムの可読性を損うので避けるべき。

スレッド拘束

データが 1 つのスレッドからしかアクセスされないようにする(スレッド間で共有させない)ことでスレッドセーフにするテクニックがスレッド拘束。
スレッド拘束のうちで最も簡単なのがスタック拘束で、これはローカル変数からしかオブジェクトにアクセスさせないようにする。ローカル変数はスレッドに紐付くスタックフレーム上に構築されるため、その変数が指すオブジェクトの参照を逸出させない限りはスレッドセーフになる。
スレッド拘束を行うための J2SE のクラスが ThreadLocal。ただしこのクラスのインスタンスはスレッド使用というコンテキストにおけるグローバル変数であるから、注意して使用すべき。
そもそも更新する必要がなければ、そのオブジェクトは immutable なものにすべき。

オブジェクトの公開

オブジェクトを安全に公開するには、次の 4 条件のいずれかを満たすことが必要。

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

static イニシャライザは JVM で内部的に同期化されているので、ここで初期化されたオブジェクトは安全に公開される。