根据书里面的实现高效本次缓存的代码,下面的代码的前提是,假设有一个很耗时的计算,并且计算结果可以重用,我想将这个计算结果缓存在map里,并且保证计算过程(代码中的Callable代码)只会被执行一次。
private final ConcurrentHashMap<A, Future<V>> cache = new ConcurrentHashMap<A, Future<V>>();
private final Computable<A, V> c;
public Memoizer(Computable<A, V> c) {
this.c = c;
}
/* (non-Javadoc)
* @see com.demo.buildingblocks.Computable#compute(java.lang.Object)
*/
@Override
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>() {
@Override
public V call() throws Exception {
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) {
launderThrowable(e);
}
}
}
public static RuntimeException launderThrowable(Throwable t) {
if (t instanceof RuntimeException)
return (RuntimeException) t;
else if (t instanceof Error)
throw (Error) t;
else
throw new IllegalStateException("Not unchecked", t);
}
我的分析是,putIfAbsent既然只能保证原子性,如果两个线程同时执行这个方法,那么会同时返回null,继而同时进入下面的if代码块,最后还是会导致compute执行了两次。
如果分析错误,那么正确的理解应该是怎样的呢?
看
ConcurrentHashMap
的源码找到的原因:而
SegmentFor
的put方法有加锁操作:这样就保证了不会有两个线程同时返回null的情况。