实践中的Java并发实践Brian Goetz提供了一个用于并发使用的高效可伸缩缓存的示例。显示类Memoizer(第108页)实现的示例的最终版本显示了这样的缓存。我想知道为什么有一个内部和外部检查if(f == null)。 第二个没有任何意义,因为:
以下是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());
}
}
}
答案 0 :(得分:5)
- 提前检查,前面的最后一步肯定会返回
的非空值 醇>cache.putIfAbsent(arg, ft);
如果只有一个线程正在调用compute
,那么cache.putIfAbsent(arg, ft);
将始终返回null
,因为之前没有值。
如果有两个或多个线程同时调用compute
方法,那么其中只有一个会从null
获得cache.putIfAbsent(arg, ft);
,其他线程将获得ft
创建了null
的帖子。
在这种情况下,其他线程会丢弃他们的FutureTask实例并继续他们从cache.putIfAbsent(arg, ft);
- 第二次检查中的ft.run()没有任何意义,因为此后会立即调用f.get()。
醇>
您需要run
FutureTask
以便get
稍后run
的价值。如果您不致电run
,您将永远无法获得价值。创建存储在缓存中的FutureTask的线程将get
,然后compute
将立即返回,因为此时它已经完成。
但是同时调用putIfAbsent
且从get
获得非空值的其他线程将转到run
调用并等待直到第一个线程完成object-fit
方法。