如何以安全有效的方式使用AtomicReference进行惰性创建和设置?

2022-09-03 00:59:23

我希望懒惰地创建一些东西并将结果缓存为优化。下面的代码是安全有效的,还是有更好的方法来做到这一点?这里是否需要比较和设置循环?

...
AtomicReference<V> fCachedValue = new AtomicReference<>();

public V getLazy() {
    V result = fCachedValue.get();
    if (result == null) {
        result = costlyIdempotentOperation();
        fCachedValue.set(result);
    }
    return result; 
} 

编辑:我在这里的例子中从coeplyIdempotentOperation()设置的值总是相同的,无论什么线程调用它。


答案 1

这不是一个伟大的系统。问题是两个线程可能会发现 ,并且两个线程都将设置为它们的新结果值。result == nullfCachedValue

您想要使用 compareAndSet(...) 方法:

AtomicReference<V> fCachedValue = new AtomicReference<>();

public V getLazy() {
    V result = fCachedValue.get();
    if (result == null) {
        result = costlyIdempotentOperation();
        if (!fCachedValue.compareAndSet(null, result)) {
            return fCachedValue.get();
        }
    }
    return result; 
} 

如果多个线程在初始化方法之前进入该方法,则它们都可能尝试创建大型结果实例。他们都会创建自己的版本,但是第一个完成该过程的人将是将结果存储在AtomicReference中的人。其他线程将完成其工作,然后释放它们并改用“赢家”创建的实例。resultresult


答案 2

出于类似的目的,我实现了 OnceEnteredCallable,它返回了一个结果。优点是其他线程不会被阻塞,并且这种代价高昂的操作被调用一次。ListenableFuture

用法(需要番石榴):

Callable<V> costlyIdempotentOperation = new Callable<>() {...};

// this would block only the thread to execute the callable
ListenableFuture<V> future = new OnceEnteredCallable<>().runOnce(costlyIdempotentOperation);

// this would block all the threads and set the reference
fCachedValue.set(future.get());

// this would set the reference upon computation, Java 8 syntax
future.addListener(() -> {fCachedValue.set(future.get())}, executorService);

推荐