弱引用的罕见用法?

A rare usage of WeakReference?

我有一个 class 其实例被底层平台初始化和使用。

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 坚持 class,这就是我尝试这样做的原因。

    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); // @@?
    }

我什至不知道如何测试这段代码。很抱歉我的问题可能有些含糊。

谢谢。

注意像

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

不是线程安全的,因为它对 instance 字段进行了两次读取;他们每个人都可以感知其他线程所做的更新。这意味着 instance == null 中的第一次读取可能会感知到另一个线程写入的较新值,而 return instance; 中的第二次读取可能会评估为先前的值,即 null。所以这个方法可以 return null 当多个线程同时执行它时。这是一种罕见的极端情况,但这种方法并不安全。您需要一个局部变量来确保测试和 return 语句使用相同的值。

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

仅当 MyAttributeConverter 仅使用 final 字段不可变时,这仍然是安全的。否则,一个线程可能 return 另一个线程在未完全构造状态下创建的实例。

您可以使用简单的方法在没有这些限制的情况下使其安全:

private static final MyAttributeConverter instance = new MyAttributeConverter();

private static MyAttributeConverter instance() {
    return instance;
}

这仍然是惰性的,因为 class 初始化只发生在 ,即方法 instance().

的第一次调用

您对 WeakReference 的使用也存在同样的问题。此外,尚不清楚为什么在局部变量中已有所需参数的两个点上诉诸方法的递归调用。

正确的实现可以简单得多:

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());
}

至少值得一试,测量应用程序的性能并将其与其他方法进行比较。

另一方面,实例似乎没有占用大量内存,也没有持有非内存资源。否则,您更担心多个实例飞来飞去的可能性。因此,另一个值得尝试和比较的变体是上面显示的使用具有延迟 class 初始化的 static final 字段并且没有机会对那个小对象进行垃圾收集的机会。


最后澄清一下。你问

Is there any chance that reference.get() inside the function.apply idiom may be null?

由于 function.apply 的计算中没有 reference.get() 调用,因此此时这样的调用不可能计算为 null。该函数接收强引用,并且由于调用代码确保此强引用不是 null,因此在调用 apply 方法期间它永远不会成为 null

通常,垃圾收集器永远不会以使用强引用的代码会注意到差异的方式改变应用程序状态(暂且不考虑更多内存的可用性)。

但是由于您具体询问了 reference.get(),垃圾收集器可能会在最后一次使用 后收集对象 regardless of method executions or local scopes. So the referent could get collected during the execution of the apply method when this method does not use the object anymore. Runtime optimizations may allow this to happen earlier than you might guess by looking at the source code,因为看起来像对象使用(例如,字段读取)可能不会在运行时使用该对象(例如,因为该值已经保存在 CPU 寄存器中,因此无需访问对象的内存)。如前所述,所有这些都没有改变方法的行为。

因此,在执行 apply 方法期间假设的 reference.get() 原则上可以评估为 null,但没有理由担心,如前所述, apply方法不变。只要需要,JVM 就会保留对象的内存,以确保正确执行该方法。

但这种解释只是为了完整性。如前所述,您不应该对不持有昂贵资源的对象使用弱引用或软引用。