在惰性初始化供应商中引用 "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 的实现。
如果 calculateExpensiveVal 在传递的 MyClass 引用上调用 getExpensiveVal,您将得到 NullPointerException。
如果 calculateExpensiveVal 创建一个线程并传递 MyClass 的引用,您可能 运行 再次遇到与第 1 点相同的问题。
但是如果你保证 calculateExpensiveVal 没有做任何事情,那么你的代码从线程安全的角度来看是正确的。 MyClass 永远不会被部分构造
由于 final JMM
提供的保证
在说即使您的 *calculateExpensiveVal 可能会使用这些点中的任何一个或两个点之后,您只会在 getExpensiveVal 方法中遇到 NullPointerException 问题。
您的 lazyProperty.get 方法已经是线程安全的,因此不会有任何问题。
因为由于 final 关键字,您将始终看到完全构造的 Supplier 对象(仅当您没有转义 'this' 对另一个线程的引用时 ) 并且您已经将 volatile 用于 value 字段,它负责查看完全构造的 value 对象。
对于业务决策应用程序,我遇到了很多必须使用惰性初始化来缓存昂贵值的情况。因此,我利用泛型和 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 的实现。
如果 calculateExpensiveVal 在传递的 MyClass 引用上调用 getExpensiveVal,您将得到 NullPointerException。
如果 calculateExpensiveVal 创建一个线程并传递 MyClass 的引用,您可能 运行 再次遇到与第 1 点相同的问题。
但是如果你保证 calculateExpensiveVal 没有做任何事情,那么你的代码从线程安全的角度来看是正确的。 MyClass 永远不会被部分构造 由于 final JMM
提供的保证在说即使您的 *calculateExpensiveVal 可能会使用这些点中的任何一个或两个点之后,您只会在 getExpensiveVal 方法中遇到 NullPointerException 问题。
您的 lazyProperty.get 方法已经是线程安全的,因此不会有任何问题。
因为由于 final 关键字,您将始终看到完全构造的 Supplier 对象(仅当您没有转义 'this' 对另一个线程的引用时 ) 并且您已经将 volatile 用于 value 字段,它负责查看完全构造的 value 对象。