DCL,volatile,Final

3125 ワード

Double check lockモードの一般的なコード:
 
 
public class Singleton {

    private static Singleton instance = null;

    private Singleton() {
    }

    public static Singleton getInstance() {
        if (instance == null) {
            synchronized (Singleton.class) {
                if (instance == null) {
                    instance = new Singleton();
                }
            }
        }

        return instance;   
    }
}

 
 
理論的には、DCLモードは、マルチスレッドでの単一モードの同時問題を優れた性能で解決することができるが、実際には、コンパイラはコード再配置(instruction reorder)の最適化手段を採用するため、これにより、不完全な初期化(メモリが割り当てられており、referenceオブジェクトが作成され、作成されたメモリアドレスを指しているが、コンストラクタがまだ実行を開始していないか、完全に実行されていない)Singletonインスタンスが返され、プログラム異常が発生することがある.
 
この問題を解決するために、JDK 1.5以降のバージョンでは、volatileおよびfinalキーワードでDCLの問題を修正できます.
 
volatileは比較的簡単で、instance変数定義の前にvolatileキーワードを付けるだけでよい.volatileキーワードを使用するとJVMのコード再配置が禁止されるため、DCLに存在する問題を解決することができる.
 
finalキーワードを使用するには、コードを見てください.
 
 
public class FinalWrapper<T> {
    public final T value;
    public FinalWrapper(T value) { 
        this.value = value; 
    }
}
 
public class Foo {
   private FinalWrapper<Helper> helperWrapper = null;
 
   public Helper getHelper() {
      FinalWrapper<Helper> wrapper = helperWrapper;
 
      if (wrapper == null) {
          synchronized(this) {
              if (helperWrapper == null) {
                  helperWrapper = new FinalWrapper<Helper>(new Helper());
              }
              wrapper = helperWrapper;
          }
      }
      return wrapper.value;
   }
}

 
まずインスタンス変数valueはfinalとして宣言するが、比較的特別な点はFooである.getHelper()メソッドでは,まずwrapperという局所変数を定義し,helperWrapperを付与し,wrapperが空であるか否かを判断して以降の処理を行う.wrapperを使用する必要があるのは、ローカル変数としてwrapperにスレッドが安全ではないという問題がないためであり、helperWrapperを直接使用すると、以前のコード再配置のため、不完全な初期化されたオブジェクトインスタンスが得られる可能性があるためです.
 
またfinalキーワードの意味に戻りますJDK 1.5以降のバージョンでは、コンパイラはfinalキーワードによって定義属性がオブジェクトインスタンス化において正しく初期化できることを保証することができる(非finalオブジェクトはコード再配列のためにこの条件を満たすことができない可能性がある)ため、最後のプログラムではwrapper.valueは常に正しく初期化されます.
 
補足1:DCLはSingletonモードで最も使いやすく,Singletonのマルチスレッドでのスレッドセキュリティの問題を解決することを目的としている.この問題を解決するためには、Singleton instanceを宣言したときに初期化(遅延ロードができない)したり、getInstance()メソッドにsynchronizedキーワード(性能問題)を追加したりするなど、上記の方法のほかに、比較的直接的な方法があります.興味深いことに、内部クラスによって遅延ロードが実現される(原理は、実際に参照されるまで内部クラスがロードされるメカニズムを利用する):
 
 
class Foo {
    private static class HelperHolder {
       public static Helper helper = new Helper();
    }
 
    public static Helper getHelper() {
        return HelperHolder.helper;
    }
}
 
補足2:volatileキーワードについては、volatile変数は各作業スレッドメモリ間の一貫性を保証しますが、volatile変数に基づく操作がスレッドで安全であることを意味しません(現在の変数値に依存し、変更される場合は、DCLを使用して単一の例の一意性を保証する必要があります).同時にvolatile変数はコード順序の不変性を保証しますが、他の非volatile変数と一緒に使用すると、コンパイラのコード再配置がトリガーされます.