在 Brian Goetz 的 Java Concurrency In Practice 中,为什么 if (f == null) 在 Memoizer 中检查了两次
In Java Concurrency In Practice by Brian Goetz, why if (f == null) was checked twice in Memoizer
Java Brian Goetz 的 Concurrency In Practice 提供了一个用于并发使用的高效可扩展缓存示例。显示 class Memoizer(第 108 页)实现的示例的最终版本显示了这样的缓存。我想知道为什么要对 if (f == null) 进行内部和外部检查。
第二个没有任何意义,因为:
- 前面有一个检查,前面的最后一步肯定 return cache.putIfAbsent(arg,
英尺);
- 第二次检查中的 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());
}
}
}
- there is a check ahead and the immediate last step ahead will definitely return a not-null value out of
cache.putIfAbsent(arg, ft);
如果只有一个线程调用compute
,那么cache.putIfAbsent(arg, ft);
将总是returnnull
,因为那里没有以前的值。
如果有两个或多个线程同时调用compute
方法,那么只有其中一个会从cache.putIfAbsent(arg, ft);
中得到null
,其他的会得到cache.putIfAbsent(arg, ft);
获得 null
的线程创建的 ft
的值。
在那种情况下,其他线程会丢弃它们的 FutureTask 实例并继续使用它们从 cache.putIfAbsent(arg, ft);
收到的实例
- the ft.run() inside the second check does not make any sense because f.get() will be called immediately thereafter.
您需要 run
一个 FutureTask
以便稍后 get
从中获取值。如果您不调用 run
,您将永远得不到任何值。创建存储在缓存中的 FutureTask 的线程将 run
它然后 get
将立即 return,因为它在那时已经完成。
但是同时调用 compute
并且从 putIfAbsent
获得非空值的其他线程将转到 get
调用并且 等待直到第一个线程用run
方法完成。
Java Brian Goetz 的 Concurrency In Practice 提供了一个用于并发使用的高效可扩展缓存示例。显示 class Memoizer(第 108 页)实现的示例的最终版本显示了这样的缓存。我想知道为什么要对 if (f == null) 进行内部和外部检查。 第二个没有任何意义,因为:
- 前面有一个检查,前面的最后一步肯定 return cache.putIfAbsent(arg, 英尺);
- 第二次检查中的 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());
}
}
}
- there is a check ahead and the immediate last step ahead will definitely return a not-null value out of
cache.putIfAbsent(arg, ft);
如果只有一个线程调用compute
,那么cache.putIfAbsent(arg, ft);
将总是returnnull
,因为那里没有以前的值。
如果有两个或多个线程同时调用compute
方法,那么只有其中一个会从cache.putIfAbsent(arg, ft);
中得到null
,其他的会得到cache.putIfAbsent(arg, ft);
获得 null
的线程创建的 ft
的值。
在那种情况下,其他线程会丢弃它们的 FutureTask 实例并继续使用它们从 cache.putIfAbsent(arg, ft);
- the ft.run() inside the second check does not make any sense because f.get() will be called immediately thereafter.
您需要 run
一个 FutureTask
以便稍后 get
从中获取值。如果您不调用 run
,您将永远得不到任何值。创建存储在缓存中的 FutureTask 的线程将 run
它然后 get
将立即 return,因为它在那时已经完成。
但是同时调用 compute
并且从 putIfAbsent
获得非空值的其他线程将转到 get
调用并且 等待直到第一个线程用run
方法完成。