揮発性のない遅延初期化/メモ化

Marcono1234:

代わりに、人々は唯一簡単にするためにそのようにそれを呼び出す、「リフレッシュ」し、ローカルキャッシュの「フラッシュする」を定義していないJavaのメモリモデルを表示されますが、実際には「たまたま-前」の関係がスッキリと何らかの形で洗い流す意味(あれば素晴らしいだろうねあなたは質問の直接の部分)ことを説明したが、できません。

これは私が本当にに関するセクションという事実と組み合わせる混同なっているJLSでのJavaのメモリモデルを理解することが容易になりますように書かれていません。

私は、次のコードで行われた仮定が正しければ、したがって、正しく実行することが保証されている場合ので、あなたは私を教えてくださいだろうか?

これは、部分的にWikipediaの記事に提供されているコードに基づいてダブルチェックロッキングしかしそこに著者は、ラッパークラスを(使用、FinalWrapper)が、この理由は、私には全く明らかにされていません。たぶんサポートするnull値を?

public class Memoized<T> {
    private T value;
    private volatile boolean _volatile;
    private final Supplier<T> supplier;

    public Memoized(Supplier<T> supplier) {
        this.supplier = supplier;
    }

    public T get() {
        /* Apparently have to use local variable here, otherwise return might use older value
         * see https://jeremymanson.blogspot.com/2008/12/benign-data-races-in-java.html
         */
        T tempValue = value;

        if (tempValue == null) {
            // Refresh
            if (_volatile);
            tempValue = value;

            if (tempValue == null) {
                // Entering refreshes, or have to use `if (_volatile)` again?
                synchronized (this) {
                    tempValue = value;

                    if (tempValue == null) {
                        value = tempValue = supplier.get();
                    }

                    /* 
                     * Exit should flush changes
                     * "Flushing" does not actually exists, maybe have to use  
                     * `_volatile = true` instead to establish happens-before?
                     */
                }
            }
        }

        return tempValue;
    }
}

また、私は、コンストラクタの呼び出しがインライン化し、初期化されていないオブジェクトへの参照が生じ並べ替えることができることを読んだことがある(参照のブログにこのコメントを)。直接サプライヤーの結果を代入するか、この2つのステップで行われなければならないことは、その後、安全ですか?

value = tempValue = supplier.get();

二つのステップ:

tempValue = supplier.get();
// Reorder barrier, maybe not needed?
if (_volatile);
value = tempValue;

編集:この質問のタイトルは少し誤解を招くおそれがあり、目標は、揮発性フィールドの減少した使用法を持っていることでした。初期化された値は、スレッドのキャッシュ内にすでにある場合は、value直接、再びメインメモリに見る必要なしにアクセスされます。

ホルガー:

簡単にすべての無関係な部分を剥ぎ取ることで表示することができますスレッドセーフではありませんあなたのコード、:

public class Memoized<T> {
    private T value;
    // irrelevant parts omitted

    public T get() {
        T tempValue = value;

        if (tempValue == null) {
            // irrelevant parts omitted
        }

        return tempValue;
    }
}

だから、value何も持っていないvolatile修飾子を、あなたが以内にそれを読んでいるget()同期せずに法とする場合以外null、任意の同期せずにそれを使用して進めます。

これだけのコードパスは、すでに、壊れたコードをしているにかかわらず割り当てるときに、あなたが何をしているかのvalue互換性の同期メカニズムを使用して、読書や側面を書き、安全な構築物は、両端を必要とするすべてのスレッドとして、。

あなたのような難解な構文を使用しているという事実はif (_volatile);、コードがすでに壊れているように、その後、無関係となります。

Wikipediaの例では、とのラッパーを使用する理由finalフィールドは使用してその不変オブジェクトされるfinalフィールドは、データ競合に対する免疫、したがって、同期操作なしでその参照を読み込むときに安全であるだけ構文です。

ラムダ式は、同じカテゴリに分類されているので、あなたのユースケースのための一例を簡単にするためにそれらを使用できることに注意してください。

public class Memoized<T> {
    private boolean initialized;
    private Supplier<T> supplier;

    public Memoized(Supplier<T> supplier) {
        this.supplier = () -> {
            synchronized(this) {
                if(!initialized) {
                    T value = supplier.get();
                    this.supplier = () -> value;
                    initialized = true;
                }
            }
            return this.supplier.get();
        };
    }

    public T get() {
        return supplier.get();
    }
}

ここでは、supplier.get()Memoized.get()の更新値読んでもsupplier、それが正しいを読んで、その場合には、同期アクションのない、valueそれは暗黙的であるために、final方法はのために古く値読み取る場合supplierの参照を、それに終わるであろうsynchronized(this)使用ブロックinitialized供給元の評価が必要であるかどうかを決定するフラグ。

以来initialized、フィールドでのみ内でアクセスされるsynchronized(this)ブロック、それは常に正しい値に評価します。最初の一つだけが評価されるのに対し、このブロックは、スレッドごとに一度最大で実行されますget()、元のサプライヤーに。その後、各スレッドが使用する() -> valueすべての同期アクションを必要とせずに値を返す、サプライヤー。

おすすめ

転載: http://43.154.161.224:23101/article/api/json?id=178427&siteId=1