在Java并发的实践中,在第106页上,它说“Memoizer3容易受到两个线程看到null并启动昂贵计算的问题的影响,因为复合操作(put-if- expensive )是在后台映射上执行的,而使用锁定无法使其成为原子操作。”我不明白为什么他们说它不能用锁来原子化。以下是原始代码:
package net.jcip.examples;
import java.util.*;
import java.util.concurrent.*;
/**
* Memoizer3
* <p/>
* Memoizing wrapper using FutureTask
*
* @author Brian Goetz and Tim Peierls
*/
public class Memoizer3 <A, V> implements Computable<A, V> {
private final Map<A, Future<V>> cache
= new ConcurrentHashMap<A, Future<V>>();
private final Computable<A, V> c;
public Memoizer3(Computable<A, V> c) {
this.c = c;
}
public V compute(final A arg) throws InterruptedException {
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 = ft;
cache.put(arg, ft);
ft.run(); // call to c.compute happens here
}
try {
return f.get();
} catch (ExecutionException e) {
throw LaunderThrowable.launderThrowable(e.getCause());
}
}
}为什么像这样的东西不能工作呢?
...
public V compute(final A arg) throws InterruptedException {
Future<V> f = null;
FutureTask<V> ft = null;
synchronized(this){
f = cache.get(arg);
if (f == null) {
Callable<V> eval = new Callable<V>() {
public V call() throws InterruptedException {
return c.compute(arg);
}
};
ft = new FutureTask<V>(eval);
f = ft;
cache.put(arg, ft);
}
}
if (f==ft) ft.run(); // call to c.compute happens here
...发布于 2013-11-22 22:20:20
当然,它可以通过使用锁来原子化,想象一下最原始的情况:你在整个函数周围有一个全局锁,那么一切都是单线程的,因此是线程安全的。我假设他们要么是其他意思,要么是普遍的误解。
您的代码甚至可以通过使用ConcurrentHashMap的putIfAbsent方法进行改进,如下所示:
public V compute(final A arg) throws InterruptedException {
Future<V> f = cache.get(arg);
if (f == null) {
final Callable<V> eval = new Callable<V>() {
public V call() throws InterruptedException {
return c.compute(arg);
}
};
final FutureTask<V> ft = new FutureTask<V>(eval);
final Future<V> previousF = cache.putIfAbsent(arg, ft);
if (previousF == null) {
f = ft;
ft.run();
} else {
f = previousF; // someone else will do the compute
}
}
return f.get();
}最后,f要么是之前添加的值,要么是初始值,代价是额外创建一个Callable,但对计算的调用不会超过一次。
https://stackoverflow.com/questions/20109897
复制相似问题