在惰性初始化供应商中引用 "this"?

Refering "this" in a lazy initialization supplier?

对于业务决策应用程序,我遇到了很多必须使用惰性初始化来缓存昂贵值的情况。因此,我利用泛型和 Supplier lambda 来封装惰性初始化。

import java.util.function.Supplier;

public final class LazyProperty<T> {
    private final Supplier<T> supplier;
    private volatile T value;

    private LazyProperty(Supplier<T> supplier) { 
        this.supplier = supplier;
    }
    public T get() { 
        if (value == null) { 
            synchronized(this) { 
                if (value == null) { 
                    value = supplier.get();
                }
            }
        }
        return value;
    }

    public static <T> LazyProperty<T> forSupplier(Supplier<T> supplier) { 
        return new LazyProperty<T>(supplier);
    }
}

但我希望在创建对象之前无法初始化属性的情况下也能使用它,因为对象只能在创建后计算该属性(通常需要上下文本身或其他对象)。但是,这通常需要在供应商函数中引用 this

public class MyClass {
    private final LazyProperty<BigDecimal> expensiveVal = 
         LazyProperty.forSupplier(() -> calculateExpensiveVal(this));

    public BigDecimal getExpensiveVal() { 
        return expensiveVal.get();
    }
}

只要我能保证 LazyProperty 的 get() 函数仅在构造 MyClass 之后调用(通过 getExpensiveVal() 方法),就不会有任何部分构造问题由于供应商中的 this 参考,对吗?

根据你展示的小代码,你应该没有任何问题,但我可能会像这样写你的 class 更明确:

public class MyClass {
    private final LazyProperty<BigDecimal> expensiveVal;

    public MyClass() {
        this.expensiveVal = LazyProperty.forSupplier(() -> calculateExpensiveVal(MyClass.this));
    }

    public BigDecimal getExpensiveVal() { 
        return expensiveVal.get();
    }
}

您的代码将有一个问题,这取决于方法 calculateExpensiveVal 的实现。

  1. 如果 calculateExpensiveVal 在传递的 MyClass 引用上调用 getExpensiveVal,您将得到 NullPointerException。

  2. 如果 calculateExpensiveVal 创建一个线程并传递 MyClass 的引用,您可能 运行 再次遇到与第 1 点相同的问题。

但是如果你保证 calculateExpensiveVal 没有做任何事情,那么你的代码从线程安全的角度来看是正确的。 MyClass 永远不会被部分构造 由于 final JMM

提供的保证

在说即使您的 *calculateExpensiveVal 可能会使用这些点中的任何一个或两个点之后,您只会在 getExpensiveVal 方法中遇到 NullPointerException 问题。

您的 lazyProperty.get 方法已经是线程安全的,因此不会有任何问题。

因为由于 final 关键字,您将始终看到完全构造的 Supplier 对象(仅当您没有转义 'this' 对另一个线程的引用时 ) 并且您已经将 volatile 用于 value 字段,它负责查看完全构造的 value 对象。