8

在 JCIP 书中,Listing 5.19 存储器的最终实现。我的问题是:

  1. 由于原子 putIfAbsent() 导致无限的 while 循环在这里?
  2. while 循环应该在 putIfAbsent() 的 impl 中而不是客户端代码中吗?
  3. while 循环是否应该在较小的范围内仅包装 putIfAbsent()?
  4. while 循环在可读性上看起来很糟糕

代码:

public class Memorizer<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 Memorizer(Computable<A, V> c) { this.c = c; }
    public V compute(final A arg) throws InterruptedException {
    while (true) { //<==== WHY?
        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());
        }
     }
   }
}
4

2 回答 2

4

1) 因为原子 putIfAbsent() 而出现了无休止的 while 循环?

此处的 while 循环用于在取消计算时重复计算(第一种情况try)。

2) while 循环应该在 putIfAbsent() 的 impl 中而不是客户端代码中吗?

不,请阅读什么putIfAbsent。它只是尝试只放置一次对象。

3) while 循环是否应该在更小的范围内仅包装 putIfAbsent()?

不,不应该。见#1。

4) While 循环在可读性上看起来很糟糕。

你可以自由地提供更好的东西。事实上,这种构造套件非常适合您必须尝试做某事直到成功进行的情况。

于 2012-12-20T23:42:34.667 回答
2

不,您不能缩小 while 循环的范围。您想要对f.get()缓存中的值执行操作。如果地图中没有 forarg的值,您希望对get()结果执行,否则您希望获取arg地图中的现有值和get()那个值。

问题是在这个实现中没有锁,所以在你检查是否有一个值和尝试插入一个值之间,另一个线程可能已经插入了它自己的值。同样,在插入失败和检索之间的情况下,该值可能已从缓存中删除(由于CancellationException)。由于这些失败案例,您需要旋转while(true)直到您可以从映射中获取规范值,或者将新值插入映射中(使您的值成为规范值)。

看起来你可以尝试更多的f.get()循环,但由于存在 的风险CancellationException,你想继续尝试。

于 2012-12-20T22:45:50.717 回答