对于业务决策应用程序,我遇到了很多情况,我必须使用延迟初始化来缓存昂贵的值。因此,我利用泛型和供应商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
引用引起的任何部分施工问题,对吗?
答案 0 :(得分:2)
基于您展示的小代码,您不应该有任何问题,但我可能会写这样的类更明确:
public class MyClass {
private final LazyProperty<BigDecimal> expensiveVal;
public MyClass() {
this.expensiveVal = LazyProperty.forSupplier(() -> calculateExpensiveVal(MyClass.this));
}
public BigDecimal getExpensiveVal() {
return expensiveVal.get();
}
}
答案 1 :(得分:1)
您的代码将有一个问题,这取决于方法 calculateExpensiveVal 的实现。
如果 calculateExpensiveVal 在传递的MyClass引用上调用getExpensiveVal,您将获得NullPointerException。
如果 calculateExpensiveVal 创建一个线程并传递MyClass的引用,您可能会遇到与第1点相同的问题。
但如果你保证 calculateExpensiveVal 没有做任何事情,那么你的代码在Thread safety Perspective中是正确的。永远不会看到MyClass部分构建 因为JMM
提供的最终 gaurantees在说完即使你的 * calculateExpensiveVal 可以使用任何一个或两个点之后,你只会在使用NullPointerException的getExpensiveVal方法中遇到问题。
您的 lazyProperty.get 方法已经是线程安全的,所以没有任何问题。
因为您最终会看到完全构造的供应商对象,因为最后的关键字( 只有在您没有转义的情况下才会出现这个&#39;对其他线程的引用 )并且您已经使用volatile来 value 字段,该字段负责查看完全构造的值对象。