在懒惰的初始化供应商中引用“this”?

时间:2015-01-05 17:33:32

标签: java multithreading concurrency lazy-initialization

对于业务决策应用程序,我遇到了很多情况,我必须使用延迟初始化来缓存昂贵的值。因此,我利用泛型和供应商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引用引起的任何部分施工问题,对吗?

2 个答案:

答案 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 的实现。

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

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

  3. 但如果你保证 calculateExpensiveVal 没有做任何事情,那么你的代码在Thread safety Perspective中是正确的。永远不会看到MyClass部分构建 因为JMM

    提供的最终 gaurantees

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

    您的 lazyProperty.get 方法已经是线程安全的,所以没有任何问题。

    因为您最终会看到完全构造的供应商对象,因为最后的关键字( 只有在您没有转义的情况下才会出现这个&#39;对其他线程的引用 )并且您已经使用volatile来 value 字段,该字段负责查看完全构造的对象。