WeakReferenceのまれな使用法?

Nov 07 2020

インスタンスが初期化され、基礎となるフラットフォームによって使用されるクラスがあります。

class MyAttributeConverter implements AttributeConverter<XX, YY> {

    public YY convertToDatabaseColumn(XX attribute) { return null; }

    public XX convertToEntityAttribute(YY dbData) { return null; }
}

何も問題はなく、メソッド参照として使用するためにいくつかの静的メソッドを追加する必要があると思いました。

    private static MyAttributeConverter instance;

    // just a lazy-initialization;
    // no synchronization is required;
    // multiple instantiation is not a problem;
    private static MyAttributeConverter instance() {
        if (instance == null) {
            instance = new MyAttributeConverter();
        }
        return instance;
    }

    // do as MyAttributeConverter::toDatabaseColumn(xx)

    public static YY toDatabaseColumn(XX attribute) {
        return instance().convertToDatabaseColumn(attribute);
    }

    public static XX toEntityAttribute(YY dbData) {
        return instance().convertToEntityAttribute(attribute);
    }

それでも何も悪いことはないようで(私は信じています)instance、クラスに固執するのは好きではありません。それが私がこれをやろうとしている理由です。

    private static WeakReference<MyAttributeConverter> reference;

    public static <R> R applyInstance(Function<? super MyAttributeConverter, ? extends R> function) {
        MyAttributeConverter referent;
        if (reference == null) {
            referent = new MyAttributeConverter();
            refernce = new WeakReference<>(referent);
            return applyInstance(function);
        }
        referent = reference.get();
        if (referent == null) {
            referent = new MyAttributeConverter();
            refernce = new WeakReference<>(referent);
            return applyInstance(function);
        }
        return function.apply(referent); // @@?
    }

私は基本的にこのコードをテストする方法さえ知りません。そして、それぞれがやや曖昧かもしれない私の質問をお詫びします。

  • これは(正しい/間違った)アプローチですか?
  • チャンスがあるreference.get()の内側function.applyイディオムかもしれはnull
  • メモリリークなどの問題が発生する可能性はありますか?
  • 私はでSoftReferenceはなく頼るべきWeakReferenceですか?

ありがとうございました。

回答

5 Holger Nov 24 2020 at 20:32

次のような方法に注意してください

// multiple instantiation is not a problem;
private static MyAttributeConverter instance() {
    if (instance == null) {
        instance = new MyAttributeConverter();
    }
    return instance;
}

instanceフィールドの読み取りが2回行われるため、スレッドセーフではありません。それらのそれぞれは、他のスレッドによって行われた更新を認識するかどうかはわかりません。これは、最初の読み込みがinstance == null別のスレッドによって書き込まれた新しい値を認識する可能性があるのに対し、2番目の読み込みreturn instance;は前の値に評価される可能性があることを意味しnullます。したがって、このメソッドはnull、複数のスレッドが同時に実行している場合に戻る可能性があります。これはまれなコーナーケースですが、それでもこの方法は安全ではありません。testとreturnステートメントが同じ値を使用するようにするには、ローカル変数が必要です。

// multiple instantiation is not a problem;
private static MyAttributeConverter instance() {
    MyAttributeConverter current = instance;
    if (current == null) {
        instance = current = new MyAttributeConverter();
    }
    return current;
}

これは、MyAttributeConverterfinalフィールドのみを使用して不変である場合にのみ安全です。そうしないと、スレッドは、別のスレッドによって作成されたインスタンスを不完全に構築された状態で返す可能性があります。

これらの制約なしに安全にするための簡単な方法を使用できます。

private static final MyAttributeConverter instance = new MyAttributeConverter();

private static MyAttributeConverter instance() {
    return instance;
}

クラスの初期化は指定されたトリガーの1つ、つまりメソッドの最初の呼び出しでのみ発生するため、これはまだ怠惰instance()です。


の使用法WeakReferenceにも同じ問題があります。さらに、ローカル変数に必要な引数がすでにある2つのポイントで、メソッドの再帰呼び出しに頼る理由は明確ではありません。

正しい実装ははるかに簡単です。

private static WeakReference<MyAttributeConverter> reference;

public static <R> R applyInstance(
    Function<? super MyAttributeConverter, ? extends R> function) {

    WeakReference<MyAttributeConverter> r = reference;
    MyAttributeConverter referent = r != null? r.get(): null;      
    if (referent == null) {
        referent = new MyAttributeConverter();
        reference = new WeakReference<>(referent);
    }
    return function.apply(referent);
}

しかし、それを使用する前に、複雑なコードが努力する価値があるかどうかを再考する必要があります。ガベージコレクションが行われたときにオブジェクトを再構築する必要性を受け入れているという事実は、同時呼び出しで複数のインスタンスを構築する可能性がある場合でも、構築が安価になることを知っていることを示唆しています。構築が安価な場合、おそらくそのインスタンスをキャッシュする必要はまったくありません。

考えてみてください

public static <R> R applyInstance(
    Function<? super MyAttributeConverter, ? extends R> function) {

    return function.apply(new MyAttributeConverter());
}

アプリケーションのパフォーマンスを測定し、他のアプローチと比較することは、少なくとも試してみる価値があります。

一方、インスタンスが大量のメモリを占有しておらず、メモリ以外のリソースを保持しているようには見えません。それ以外の場合と同様に、複数のインスタンスが飛び交う可能性についてより心配していました。したがって、試して比較する価値のある他のバリアントは、static finalレイジークラスの初期化があり、その小さなオブジェクトをガベージコレクションする機会がないフィールドを使用した上記のバリアントです。


最後にもう1つ説明します。あなたは尋ねました

チャンスがあるreference.get()の内側function.applyイディオムかもしれはnull

reference.get()の評価内には呼び出しがないfunction.applyためnull、この時点でそのような呼び出しが評価される可能性はありません。関数は強力な参照を受け取ります。呼び出しコードはこの強力な参照がないことを保証しているため、メソッドの呼び出し中nullには決してなりません。nullapply

一般に、ガベージコレクターは、強力な参照を使用するコードが違いに気付くような方法でアプリケーションの状態を変更することはありません(より多くのメモリの可用性を脇に置きます)。

あなたは具体的には約尋ねたので、しかしreference.get()、ガベージコレクタは、オブジェクトを収集することがあり、その最後の使用後に、メソッドの実行、またはローカルスコープに関係なく。したがって、applyこのメソッドがオブジェクトを使用しなくなったときに、メソッドの実行中に指示対象が収集される可能性があります。実行時の最適化により、ソースコードを見て推測するよりも早くこれを実行できる場合があります。これは、オブジェクトの使用のように見えるもの(フィールドの読み取りなど)が実行時にオブジェクトを使用しない場合があるためです(たとえば、その値がすでにCPUレジスタ、オブジェクトのメモリにアクセスする必要がなくなります)。前述のように、すべてメソッドの動作を変更することなく。

したがって、メソッドreference.get()の実行中の仮説applyは原則としてと評価できnullますが、前述のように、applyメソッドの動作は変更されません。JVMは、この正しいメソッドの実行を保証するために必要な限り、オブジェクトのメモリを保持します。

しかし、その説明は完全を期すためのものでした。前述のように、高価なリソースを保持していないオブジェクトには、弱い参照やソフト参照を使用しないでください。