首页 > 解决方案 > WeakReference 的罕见用法?

问题描述

我有一个类,其实例由底层平面格式初始化和使用。

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

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

谢谢你。

标签: javaweak-referencessoft-references

解决方案


请注意,类似的方法

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

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

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

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

您可以使用简单的方法使其安全,而不受这些限制:

private static final MyAttributeConverter instance = new MyAttributeConverter();

private static MyAttributeConverter instance() {
    return instance;
}

这仍然是惰性的,因为类初始化只发生在一个指定的触发器上,即方法的第一次调用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());
}

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

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


最后一个澄清。您问

reference.get()成语里面有没有function.apply可能null

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

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

但是由于您特别询问reference.get(),垃圾收集器可能会在对象最后一次使用后收集它而不管方法执行或本地范围如何apply因此,当此方法不再使用对象时,可以在方法执行期间收集所指对象。运行时优化可能会比您通过查看源代码所猜测的更早发生这种情况,因为看起来像对象使用(例如字段读取)可能不会在运行时使用该对象(例如,因为该值已经保存在CPU 寄存器,无需访问对象的内存)。如前所述,所有这些都不会改变方法的行为。

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

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


推荐阅读