JPCERT コーディネーションセンター

VNA01-J. 不変オブジェクトへの共有参照の可視性を確保する

不変(immutable)オブジェクトへの共有参照は、参照が更新されるとただちに複数スレッド間で可視となる、と誤解されることが多い。たとえば、不変オブジェクトだけ参照するフィールドを含むクラスは、クラス自身が不変であり、それゆえスレッドセーフである、という誤った認識を持つプログラマは少なくない。

『プログラミング言語Java』第4版の14.10.2節「finalフィールドとセキュリティ」[Arnold 2006]には、以下のように記述されている

問題は、共有オブジェクトが不変であっても、共有オブジェクトにアクセスするために使用される参照もまた共有されており、多くの場合、この参照は可変であるということである。したがって、プログラムを正しく同期するには、共有参照へのアクセスを同期する必要がある。しかし、その必要性が認識されず、共有参照へのアクセスが同期されないことが多い。

不変オブジェクトへの参照および可変オブジェクトへの参照は、どちらもすべてのスレッドに対して可視にしなくてはならない。不変オブジェクトは、複数のスレッド間で安全に共有することができる。しかし、可変オブジェクトの場合、オブジェクトの生成が完了する前に、その参照が可視となる場合がある。「TSM03-J. 初期化が完了していないオブジェクトを公開しない」では、オブジェクトの生成と可視性に特化した問題について記述している。

違反コード

以下の違反コードは、不変クラスHelper と可変クラス Foo から構成されている。

// 不変のHelperクラス
public final class Helper {
  private final int n;

  public Helper(int n) {
    this.n = n;
  }
  // ...
}

final class Foo {
  private Helper helper;

  public Helper getHelper() {
    return helper;
  }

  public void setHelper(int num) {
    helper = new Helper(num);
  }
}

getHelper()メソッドは可変のhelperフィールドを公開する。Helper クラスは不変であるため、初期化後に変更することはできない。さらに、オブジェクトは常に、その参照が可視となる前に適切に生成される。これは「TSM03-J. 初期化が完了していないオブジェクトを公開しない」に適合している。しかし、他のスレッドが、Fooクラスのhelperフィールドの、最新でない参照を読み取ることはありうる。

適合コード (メソッドの同期)

以下の適合コードでは、Fooクラスのメソッドを同期しており、各スレッドはHelperオブジェクトへの最新でない参照を得ることはない。

final class Foo {
  private Helper helper;

  public synchronized Helper getHelper() {
    return helper;
  }

  public synchronized void setHelper(int num) {
    helper = new Helper(num);
  }
}

不変クラス Helper に変更はない。

適合コード (Volatile変数)

不変のメンバオブジェクトへの参照は、volatile宣言することで可視にすることができる。

final class Foo {
  private volatile Helper helper;

  public Helper getHelper() {
    return helper;
  }

  public void setHelper(int num) {
    helper = new Helper(num);
  }
}

不変クラス Helperに変更はない。

適合コード (java.util.concurrent パッケージ)

以下の解決法では、不変オブジェクトHelperへの可変である参照を、アトミックに更新可能なAtomicReferenceでラップしている。

final class Foo {
  private final AtomicReference<Helper> helperRef =
      new AtomicReference<Helper>();

  public Helper getHelper() {
    return helperRef.get();
  }

  public void setHelper(int num) {
    helperRef.set(new Helper(num));
  }
}

不変クラスHelperに変更はない。

リスク評価

不変オブジェクトへの参照のみを含むクラスは不変であるという仮定は誤りであり、スレッドの安全性に関する深刻な問題を引き起こす可能性がある。

ルール 深刻度 可能性 修正コスト 優先度 レベル
VNA01-J P4 L3
参考文献
[API 2006]  
[JPL 2006] 14.10.2, Final Fields and Security
翻訳元

これは以下のページを翻訳したものです。

VNA01-J. Ensure visibility of shared references to immutable objects (revision 77)

Top へ

最新情報(RSSメーリングリストTwitter
Copyright © 1996-2020 JPCERT/CC All Rights Reserved.