29

二重チェックのロックを行う方法について、この質問を読みました。

// Double-check idiom for lazy initialization of instance fields
private volatile FieldType field;
FieldType getField() {
    FieldType result = field;
    if (result == null) { // First check (no locking)
        synchronized(this) {
            result = field;
            if (result == null) // Second check (with locking)
                field = result = computeFieldValue();
        }
    }
    return result;
}

私の目的は、フィールド (シングルトンではない) の遅延読み込みを volatile 属性なしで機能させることです。フィールド オブジェクトは、初期化後に変更されることはありません。

私の最終的なアプローチをいくつかテストした後:

    private FieldType field;

    FieldType getField() {
        if (field == null) {
            synchronized(this) {
                if (field == null)
                    field = Publisher.publish(computeFieldValue());
            }
        }
        return fieldHolder.field;
    }



public class Publisher {

    public static <T> T publish(T val){
        return new Publish<T>(val).get();
    }

    private static class Publish<T>{
        private final T val;

        public Publish(T val) {
            this.val = val;
        }

        public T get(){
            return val;
        }
    }
}

再利用可能な Publisher クラスでシンプルさを保ちながら、揮発性を必要としないため、アクセス時間が短縮される可能性があります。


jcstressを使用してこれをテストしました。SafeDCLFinal は期待どおりに機能しましたが、UnsafeDCLFinal は一貫性がありませんでした (期待どおり)。この時点で 99% 確実に動作しますが、間違っていることを証明してください。でコンパイルしmvn clean install -pl tests-custom -amて実行しjava -XX:-UseCompressedOops -jar tests-custom/target/jcstress.jar -t DCLFinalます。以下のテスト コード (主に変更されたシングルトン テスト クラス):

/*
 * SafeDCLFinal.java:
 */

package org.openjdk.jcstress.tests.singletons;

public class SafeDCLFinal {

    @JCStressTest
    @JCStressMeta(GradingSafe.class)
    public static class Unsafe {
        @Actor
        public final void actor1(SafeDCLFinalFactory s) {
            s.getInstance(SingletonUnsafe::new);
        }

        @Actor
        public final void actor2(SafeDCLFinalFactory s, IntResult1 r) {
            r.r1 = Singleton.map(s.getInstance(SingletonUnsafe::new));
        }
    }

    @JCStressTest
    @JCStressMeta(GradingSafe.class)
    public static class Safe {
        @Actor
        public final void actor1(SafeDCLFinalFactory s) {
            s.getInstance(SingletonSafe::new);
        }

        @Actor
        public final void actor2(SafeDCLFinalFactory s, IntResult1 r) {
            r.r1 = Singleton.map(s.getInstance(SingletonSafe::new));
        }
    }


    @State
    public static class SafeDCLFinalFactory {
        private Singleton instance; // specifically non-volatile

        public Singleton getInstance(Supplier<Singleton> s) {
            if (instance == null) {
                synchronized (this) {
                    if (instance == null) {
//                      instance = s.get();
                        instance = Publisher.publish(s.get(), true);
                    }
                }
            }
            return instance;
        }
    }
}

/*
 * UnsafeDCLFinal.java:
 */

package org.openjdk.jcstress.tests.singletons;

public class UnsafeDCLFinal {

    @JCStressTest
    @JCStressMeta(GradingUnsafe.class)
    public static class Unsafe {
        @Actor
        public final void actor1(UnsafeDCLFinalFactory s) {
            s.getInstance(SingletonUnsafe::new);
        }

        @Actor
        public final void actor2(UnsafeDCLFinalFactory s, IntResult1 r) {
            r.r1 = Singleton.map(s.getInstance(SingletonUnsafe::new));
        }
    }

    @JCStressTest
    @JCStressMeta(GradingUnsafe.class)
    public static class Safe {
        @Actor
        public final void actor1(UnsafeDCLFinalFactory s) {
            s.getInstance(SingletonSafe::new);
        }

        @Actor
        public final void actor2(UnsafeDCLFinalFactory s, IntResult1 r) {
            r.r1 = Singleton.map(s.getInstance(SingletonSafe::new));
        }
    }

    @State
    public static class UnsafeDCLFinalFactory {
        private Singleton instance; // specifically non-volatile

        public Singleton getInstance(Supplier<Singleton> s) {
            if (instance == null) {
                synchronized (this) {
                    if (instance == null) {
//                      instance = s.get();
                        instance = Publisher.publish(s.get(), false);
                    }
                }
            }
            return instance;
        }
    }
}

/*
 * Publisher.java:
 */

package org.openjdk.jcstress.tests.singletons;

public class Publisher {

    public static <T> T publish(T val, boolean safe){
        if(safe){
            return new SafePublish<T>(val).get();
        }
        return new UnsafePublish<T>(val).get();
    }

    private static class UnsafePublish<T>{
        T val;

        public UnsafePublish(T val) {
            this.val = val;
        }

        public T get(){
            return val;
        }
    }

    private static class SafePublish<T>{
        final T val;

        public SafePublish(T val) {
            this.val = val;
        }

        public T get(){
            return val;
        }
    }
}

Java 8 でテスト済みですが、少なくとも Java 6 以降では動作するはずです。ドキュメントを見る


しかし、これが機能するかどうかは疑問です:

    // Double-check idiom for lazy initialization of instance fields without volatile
    private FieldHolder fieldHolder = null;
    private static class FieldHolder{
        public final FieldType field;
        FieldHolder(){
            field = computeFieldValue();
        }
    }

    FieldType getField() {
        if (fieldHolder == null) { // First check (no locking)
            synchronized(this) {
                if (fieldHolder == null) // Second check (with locking)
                    fieldHolder = new FieldHolder();
            }
        }
        return fieldHolder.field;
    }

または多分:

    // Double-check idiom for lazy initialization of instance fields without volatile
    private FieldType field = null;
    private static class FieldHolder{
        public final FieldType field;

        FieldHolder(){
            field = computeFieldValue();
        }
    }

    FieldType getField() {
        if (field == null) { // First check (no locking)
            synchronized(this) {
                if (field == null) // Second check (with locking)
                    field = new FieldHolder().field;
            }
        }
        return field;
    }

または:

    // Double-check idiom for lazy initialization of instance fields without volatile
    private FieldType field = null;

    FieldType getField() {
        if (field == null) { // First check (no locking)
            synchronized(this) {
                if (field == null) // Second check (with locking)
                    field = new Object(){
                        public final FieldType field = computeFieldValue();
                    }.field;
            }
        }
        return field;
    }

これは、このオラクルのドキュメントに基づいて機能すると思います:

final フィールドの使用モデルは単純です。オブジェクトのコンストラクタでオブジェクトの final フィールドを設定します。オブジェクトのコンストラクターが終了する前に、別のスレッドが参照できる場所に構築中のオブジェクトへの参照を書き込まないでください。これに従えば、オブジェクトが別のスレッドから見られるとき、そのスレッドは常に、そのオブジェクトの final フィールドの正しく構築されたバージョンを認識します。また、少なくとも最終フィールドと同じくらい最新の最終フィールドによって参照されるオブジェクトまたは配列のバージョンも表示されます。

4

5 に答える 5

30

まず最初に、あなたがやろうとしていることはせいぜい危険です。人々がファイナルでカンニングしようとするとき、私は少し緊張しています。Java 言語はvolatile、スレッド間の一貫性を処理するための頼りになるツールとして を提供します。これを使って。

とにかく、関連するアプローチは 「Javaでの安全な公開と初期化」で次のように説明されています。

public class FinalWrapperFactory {
  private FinalWrapper wrapper;

  public Singleton get() {
    FinalWrapper w = wrapper;
    if (w == null) { // check 1
      synchronized(this) {
        w = wrapper;
        if (w == null) { // check2
          w = new FinalWrapper(new Singleton());
          wrapper = w;
        }
      }
    }
    return w.instance;
  }

  private static class FinalWrapper {
    public final Singleton instance;
    public FinalWrapper(Singleton instance) {
      this.instance = instance;
    }
  }
}

平たく言えば、このように機能します。synchronizednull として観察すると、適切な同期が得られます。つまり、最初のチェックを完全に削除してメソッド本体全体wrapperに拡張すると、コードは明らかに正しいことになります。非 null が見られた場合、それは完全に構築され、すべてのフィールドが表示されることを保証します。これは、 の際どい読み取りから回復します。synchronizedfinalFinalWrapperwrapperSingletonwrapper

FinalWrapper値自体ではなく、フィールド内で引き継がれることに注意してください。もし. instance_ FinalWrapperこれがあなたが機能しない理由Publisher.publishです: final フィールドに値を入れて読み返し、安全でない状態で公開することは安全ではありませんinstance

wrapperまた、 nullを発見し、その値を使用する場合は、ロックの下で「フォールバック」読み取りを行うように注意する必要があります。in return ステートメントの 2 回目 (3 回目) の読み取りを行うwrapperと、正当なレースの準備が整い、正確性が損なわれます。

final編集: ちなみに、公開しているオブジェクトが内部で -s で覆われている場合、 の仲介者を切り取り、それ自体FinalWrapperを公開することができinstanceます。

編集 2: LCK10-J も参照してください。double-checked locked idiom の正しい形式を使用し、コメントでいくつかの議論を行ってください。

于 2015-05-05T09:09:38.100 に答える
2

@Kicsiが言及した「ダブルチェックロックが壊れている」宣言を引用すると、最後のセクションは次のとおりです。

ダブルチェックされた不変オブジェクトのロック

ヘルパーのすべてのフィールドが final であるなど、ヘルパーが不変オブジェクトである場合、ダブルチェック ロックは volatile フィールドを使用しなくても機能します。不変オブジェクト (String や Integer など) への参照は、int や float とほぼ同じように動作する必要があるという考え方です。不変オブジェクトへの参照の読み取りと書き込みはアトミックです。

(強調は私です)

は不変であるためFieldHolder、実際にはキーワードは必要ありませんvolatile。他のスレッドは常に適切に初期化されFieldHolderた . 私が理解している限り、 はFieldType他のスレッドから を介してアクセスできるようになる前に常に初期化されますFieldHolder

FieldTypeただし、が不変でない場合は、適切な同期が引き続き必要です。volatile結果として、キーワードを避けることで多くのメリットがあるかどうかはわかりません。

ただし、不変の場合はFieldHolder、上記の引用に従って、まったく必要ありません。

于 2015-04-30T15:25:53.353 に答える
-1

いいえ、これはうまくいきません。

finalvolatile が保証するスレッド間の可視性は保証されません。あなたが引用したOracleドキュメントは、他のスレッドは常にオブジェクトの最終フィールドの正しく構築されたバージョンを見ると言っています。finalオブジェクトコンストラクターの実行が終了するまでに、すべての最終フィールドが構築および設定されていることを保証します。したがって、 objectFooに final フィールドが含まれている場合bar、のコンストラクターが終了するまでに構築barれることが保証されます。Foo

ただし、フィールドによって参照されるオブジェクトfinalは依然として変更可能であり、そのオブジェクトへの書き込みは異なるスレッド間で正しく表示されない場合があります。

したがって、あなたの例では、他のスレッドが作成されたFieldHolderオブジェクトを表示することが保証されておらず、別のオブジェクトを作成する可能性があります。または、FieldTypeオブジェクトの状態に変更が発生した場合、他のスレッドがこれらの変更を表示することは保証されません。このfinalキーワードは、他のスレッドがオブジェクトを認識したらFieldType、そのコンストラクターが呼び出されたことを保証するだけです。

于 2015-04-26T21:48:02.873 に答える