在Brian Goetz的Java Concurrency In Practice中,为什么在Memoizer中检查了两次(f == null)

Den*_*ang 2 java concurrency thread-safety concurrenthashmap java.util.concurrent

Brian Goetz的Java Concurrency实践提供了一个用于并发使用的高效可伸缩缓存的示例.显示类Memoizer(第108页)实现的示例的最终版本显示了这样的缓存.我想知道为什么有一个内部和外部检查if(f == null).第二个没有任何意义,因为:

  1. 前面有一个检查,前面的最后一步肯定会从cache.putIfAbsent(arg,ft)中返回一个非空值.
  2. 第二次检查中的ft.run()没有任何意义,因为此后会立即调用f.get().

这是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());
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

Erw*_*idt 6

  1. 前面有一个检查,前面的最后一步肯定会返回一个非空值 cache.putIfAbsent(arg, ft);

如果只有一个线程调用compute,那么cache.putIfAbsent(arg, ft);始终返回null,因为没有先前的值.

如果有两个或多个线程同时调用该compute方法,那么只有其中一个线程将null离开cache.putIfAbsent(arg, ft);,其他ft线程将获得null创建的线程的值.

在这种情况下,其他线程丢弃他们的FutureTask实例并继续他们收到的实例 cache.putIfAbsent(arg, ft);

  1. 第二次检查中的ft.run()没有任何意义,因为此后会立即调用f.get().

您需要runa FutureTask以便get稍后从中获取值.如果你不打电话,run你就不会得到任何价值.创建了FutureTask是得到了存储在缓存中,将线程run,然后get立即返回,因为它已经在该点完成.

但是compute同时调用并且从中得到非空值的其他线程putIfAbsent将转到get调用并等待第一个线程完成该run方法.