Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to do a lazy create and set with AtomicReference in a safe and efficient manner?

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.

like image 588
marathon Avatar asked Nov 20 '13 04:11

marathon


2 Answers

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'.

like image 131
rolfl Avatar answered Sep 20 '22 12:09

rolfl


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);
like image 45
Andrey Chaschev Avatar answered Sep 20 '22 12:09

Andrey Chaschev