【问题标题】:A rare usage of WeakReference?WeakReference 的罕见用法?
【发布时间】:2020-11-07 03:54:42
【问题描述】:

我有一个类,其实例已被底层 flatform 初始化和使用。

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吗?

谢谢。

【问题讨论】:

  • 这里有几个代码味道,尤其是静态状态的使用和您“需要”方法引用的概念。这似乎是一个 JPA 转换器,在这种情况下几乎没有任何意义;这些只是实现接口的普通(实例)对象。
  • 我不明白你在问什么,但你将 static 字段包装在 WeakReference 中?你知道那个静态的referent什么时候会消失吗?
  • MyAttributeConveterMyAttributeConverter 一样吗?我问是因为你多次使用这两个不同的标识符,编译器通常会告诉你一个是否是另一个拼写错误的版本。
  • @Holger 是的,伙计!有错别字。

标签: java weak-references soft-references


【解决方案1】:

注意类似的方法

// 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;
}

这仍然是惰性的,因为类初始化只发生在one of the specified triggers,即方法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 字段的变体,该字段具有延迟类初始化并且没有机会垃圾收集那个小对象。


最后的澄清。你问了

function.apply 成语中的reference.get() 是否有可能是null

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

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

但由于您特别询问了reference.get(),垃圾收集器可能会在最后一次使用后收集对象,regardless of method executions or local scopes。因此,当该方法不再使用对象时,可以在apply 方法的执行期间收集所指对象。 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 将根据需要保留对象的内存,以确保该方法正确执行。

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-06-13
    • 2011-03-02
    • 1970-01-01
    • 2017-04-12
    • 2011-03-15
    • 2019-04-11
    相关资源
    最近更新 更多