I'm looking to lazily create something and cache the results as an optimization. Is the code below safe and efficient, or is there a better way to do this? Is the compare and set loop needed here?
...
AtomicReference<V> fCachedValue = new AtomicReference<>();
public V getLazy() {
V result = fCachedValue.get();
if (result == null) {
result = costlyIdempotentOperation();
fCachedValue.set(result);
}
return result;
}
edit: The value being set in my example here from costlyIdempotentOperation() would always be the same no matter what thread called it.
That is not a great system. The problem is that two threads may find that the result == null
, and both will set the fCachedValue
to their new result value.
You want to use the compareAndSet(...) method:
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;
}
If multiple threads get in to the method before it has been initialized, they may all try to create the large result instance. They will all create their own version of it, but the first one to complete the process will be the one who gets to store their result in the AtomicReference. The other threads will complete their work, then dispose of their result
and instead use the result
instance created by the 'winner'.
For a similar purpose I implemented OnceEnteredCallable which returns a ListenableFuture
for a result. The advantage is that the other threads are not being blocked and this costly operation is being called once.
Usage (requires Guava):
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);
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With