Java Concurrency In Practice by Brian Goetz provides an example of a efficient scalable cache for concurrent use. The final version of the example showing the implementation for class Memoizer (pg 108) shows such a cache. I am wondering why there were an inner and outer check of the if (f == null). The second one does not make any sense because:
Here is the code for Memoizer:
public class Memoizer<A, V> implements Computable<A, V> {
private final ConcurrentMap<A, Future<V>> cache
= new ConcurrentHashMap<A, Future<V>>();
private final Computable<A, V> c;
public Memoizer(Computable<A, V> c) { this.c = c; }
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>() {
public V call() throws InterruptedException {
return c.compute(arg);
}
};
FutureTask<V> ft = new FutureTask<V>(eval);
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) {
throw launderThrowable(e.getCause());
}
}
}
- there is a check ahead and the immediate last step ahead will definitely return a not-null value out of
cache.putIfAbsent(arg, ft);
If there is only a single thread calling compute
, then cache.putIfAbsent(arg, ft);
will always return null
, as there is no previous value.
If there are two or more threads calling the compute
method at the same time, then only one of them will get null
out of cache.putIfAbsent(arg, ft);
, the others will get the value of ft
that the thread which got null
created.
In that case, the other threads throw away their instance of FutureTask and continue with the instance that they received from cache.putIfAbsent(arg, ft);
- the ft.run() inside the second check does not make any sense because f.get() will be called immediately thereafter.
You need to run
a FutureTask
in order to get
the value from it later. If you don't call run
you won't ever get a value. The thread that created the FutureTask that got stored in the cache, will run
it and then get
will return immediately, because it is already complete at that point.
But the other threads that called compute
at the same time and that got a non-null value from putIfAbsent
, will go to the get
call and wait until the first thread is done with the run
method.