Search code examples
javamultithreadingconcurrencyconcurrenthashmapfuturetask

What will happen when two threads execute cache.putIfAbsent at the same time?


I am learning Java Concurrency in Practice, but some code confused me:

private final ConcurrentHashMap<A, Future<V>> cache = new ConcurrentHashMap<A, Future<V>>();

private final Computable<A, V> c;

public Memoizer(Computable<A, V> c) {
    this.c = c;
}

/* (non-Javadoc)
 * @see com.demo.buildingblocks.Computable#compute(java.lang.Object)
 */
@Override
public V compute(final A arg) throws InterruptedException {

    while (true) {
        Future<V> f = cache.get(arg);
        if (f == null) {
            // 
            Callable<V> eval = new Callable<V>() {
                @Override
                public V call() throws Exception {
                    return c.compute(arg);
                }
            };
            FutureTask<V> ft = new FutureTask<V>(eval);
            // what will happen when two threads arrive here at the same time?
            f = cache.putIfAbsent(arg, ft);
            if (f == null) {
                f = ft;
                ft.run();
            }
        }
        try {
            return f.get();
        } catch (CancellationException e) {
            cache.remove(arg, f);
        } catch (ExecutionException e) {
            launderThrowable(e);
        }
    }
}

I just can't understand, since putIfAbsent can only guarantee put operation is atomatic,and they all return null, if the two threads can both enter the run method?


Solution

  • putIfAbsent guarantees thread safety not only in the sense that it won't corrupt your data, but also in the sense that it always works on an up-to-date copy of the data.

    Also, not it returns the previous value from the map, if such a value existed. So the first call to putIfAbsent would succeed, and return null, since there is no previous value. The second call will block until the first one succeeds, and then return the first value that was put in the map, causing the second run() to never be called.