WeakReference 的罕见用法?

Jin*_*won 1 java weak-references soft-references

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

class MyAttributeConverter implements AttributeConverter<XX, YY> {

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

    public XX convertToEntityAttribute(YY dbData) { return null; }
}
Run Code Online (Sandbox Code Playgroud)

没有错,我想我需要添加一些静态方法以用作方法引用。

    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);
    }
Run Code Online (Sandbox Code Playgroud)

仍然没有任何问题(我相信),我不喜欢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); // @@?
    }
Run Code Online (Sandbox Code Playgroud)

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

  • 这是一种(正确/错误)方法吗?
  • 成语reference.get()里面有没有function.apply可能null
  • 有没有可能出现内存泄漏等问题?
  • 我应该依赖SoftReference而不是WeakReference吗?

谢谢你。

Hol*_*ger 5

请注意,像这样的方法

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

不是线程安全的,因为它包含对该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;
}
Run Code Online (Sandbox Code Playgroud)

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

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

private static final MyAttributeConverter instance = new MyAttributeConverter();

private static MyAttributeConverter instance() {
    return instance;
}
Run Code Online (Sandbox Code Playgroud)

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

但是在您打算使用它之前,您应该重新考虑复杂的代码是否值得付出努力。您接受在对象被垃圾回收后重建对象的需要,甚至可能在并发调用时构造多个实例,这一事实表明您知道构造将很便宜。当构造便宜时,您可能根本不需要缓存它的实例。

只是考虑

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

    return function.apply(new MyAttributeConverter());
}
Run Code Online (Sandbox Code Playgroud)

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

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


最后一个澄清。你问

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

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

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

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

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

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