読者です 読者をやめる 読者になる 読者になる

この日記は私的なものであり、所属会社の見解ではありません。 GitHub: takahashikzn

single-check idiom ?

Java


先程、これの10章を読み終えました。

Effective Java (Java Series)

Effective Java (Java Series)


284ページの内容がちょっと面白かったので、ご紹介します。

(284ページの意訳)
JDK5からはメモリモデルのあいまいさが減ったため、volatileが正しく動作するようになった。

初期化が何回か実行されてしまうことを許容できるなら、遅延ロードをこんな風に書いても大丈夫になったよ。


(コード1)

// Single-check idiom - can cause repeated initialization!
private volatile FieldType field;

private FieldType getField() {
    FieldType result = field;
    if (result == null)
        field = result = computeFieldValue();

    return result;
}


とあります。

さて、このコードは奇妙だ。
ローカル変数resultが無意味に見えるんだけど。


これと何が違うんだ?

(コード2)

/* 1 */  private volatile FieldType field;
/* 2 */
/* 3 */  private FieldType getField() {
/* 4 */      if (field == null)
/* 5 */          field = computeFieldValue();
/* 6 */
/* 7 */      return field;
/* 8 */  }


…などと思いつつ暫く悩んでいたわけですが、やっとresultの意味が判りました。

コード1とコード2では、一点だけ違いがあるのです。

何が違う?

例えば、二つのスレッドが同時に、コード2におけるgetFieldを実行したときのワーストケースを考えてみます。
初期状態でfieldはnullとします。

  1. スレッド1: 4行目、チェックをパスする。
  2. スレッド1: 5行目、computeFieldValue()の結果(値Aとする)をスタックに積む。
    (fieldはまだnullのまま)
  3. スレッド2: 4行目、チェックをパスする。
  4. スレッド2: 5行目、computeFieldValue()の結果(値A'とする)をスタックに積む。
    (fieldはまだnullのまま)
  5. スレッド1: 5行目、値Aをfieldへ格納する。
  6. スレッド2: 5行目、値A'をfieldへ格納する。
  7. スレッド1: 7行目、fieldの値、すなわち値A'をリターンする。 
  8. スレッド2: 7行目、fieldの値、すなわち値A'をリターンする。 


上記のステップ7で、スレッド1は自分で計算した値Aでなく、値A'を返してしまうコトになります。

コード1の場合、ローカル変数resultへ計算結果を退避しているため、ワーストケースでもスレッド1は値A、スレッド2は値A'を返すことができます。


実際には、このようなケースでは値Aと値A'は完全に等価であることがほとんどだと思うので、
コード2のように書いても全く問題ないんですけどね。