并发HashMap的客户端锁定原子性

时间:2018-06-03 07:46:13

标签: java multithreading locking concurrenthashmap atomicity

Java Concurrency In Practice 中,提出了Computable<>函数的缓存类,其中包含给定的示例代码:

public class Memorizer3<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 Memorizer3(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(e.getCause());
        }
    } 
}

然后继续解释这是几乎完美;从if(f == null)开始的put-if-absent序列给出了一个微小但可能的窗口,当2个或更多并发线程可能观察到缓存不包含f并同时尝试计算结果时,而不是等待对于单个线程。

为了解决这个问题,它建议使用putIfAbsent() API提供的预定义ConcurrentHashMap方法。这很有道理,但有一点可以证明它是令人费解的。

  

Memorizer3容易受到此问题的影响,因为在支持地图上执行了复合操作(putIfAbsent) 无法使用锁定使其成为原子。

为什么会这样?我知道ConcurrentHashMap有自己的内部锁,因此尝试使用自身获取锁定不会阻止其他线程“恶意”进行更改。

然而,给出的当前示例遵循一些不同的模式,使得它似乎可以在客户端获得锁定(可能不是针对此特定用途,而是针对API不支持的任何其他复合操作)。

它不发布cachecache实际上是不可变的,使得该对象完全被这个类封装。鉴于这种设置,是不是可以简单地使用客户端锁(使用对象cache本身,比如说)Memorizer3的类函数来实现原子性?

无论情况如何,基于对ConcurrentHashMaps的锁定实现原子性是否真的不可能,或者报价是否仅适用于某些条件?

*参考5.6。构建一个高效,可扩展的结果缓存,清单5.18。

0 个答案:

没有答案