在JCIP示例“清单5.19 Memoizer”中,应在ExecutionException发生时删除缓存

时间:2013-12-16 10:59:31

标签: java concurrency java.util.concurrent

许多人阅读“实践中的Java并发”,但为方便起见,将代码粘贴在此处。

5.19 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.launderThrowable(e.getCause());
            }
        }
    }
}

我的问题是在catch(ExecutionException e)中,应添加以下代码段:

cache.remove(arg, f);

正确?

2 个答案:

答案 0 :(得分:1)

如果compute是幂等的,那么从缓存中删除它会导致不必要的计算,因为下一次调用compute的结果也将是ExecutionException

如果您将f留在缓存中,则下一次调用将再次运行f.get(),这将立即返回ExecutionException,而无需实际执行计算。

所以它看起来更有效率。

请注意,人们会期望compute是幂等的:如果结果可能会在将来发生变化,那么缓存结果是没有意义的。

答案 1 :(得分:1)

Per JCIP(p.106):

  

... Memoizer如果检测到计算被取消,则从缓存中删除Future;如果计算可能在将来的尝试中成功,则可能还需要在检测到Future 时删除RuntimeException

(强调补充。)

如果没有,那就没有意义了,正如assylias所述。