JDK 8:ConcurrentHashMap.compute有时似乎允许多次调用重新映射函数

时间:2019-07-08 19:27:03

标签: java-8 concurrenthashmap atomicity

我正在开发一个高度并发的应用程序,该应用程序使用基于ConcurrentHashMap的对象缓存。我对ConcurrentHashMap的理解是,对“计算”方法族的调用保证了关于重映射功能的原子性。但是,我发现似乎是异常行为:有时,重映射函数被多次调用。

我的代码中的以下代码片段显示了这种情况是如何发生的,以及解决该问题的方法:

private ConcurrentMap<Integer, Object> cachedObjects 
        = new ConcurrentHashMap<>(100000);

private ReadWriteLock externalLock = new ReentrantReadWriteLock();
private Lock visibilityLock = externalLock.readLock();

...

public void update(...) {
    ...
    Reference<Integer> lockCount = new Reference<>(0);
    try {
        newStats = cachedObjects.compute(objectId, (key, currentStats) -> {

            ...

            visibilityLock.lock();
            lockCount.set(lockCount.get() + 1);
            return updateFunction.apply(objectId, currentStats);
        });
    } finally {
        int count = lockCount.get();
        if (count > 1) {
            logger.debug("NOTE! visibilityLock acquired {} times!", count);
        }
        while (count-- > 0) {
            // if locked, then unlock. The unlock is outside the compute to
            // ensure the lock is released only after the modification is
            // visible to an iterator created from the active objects hashmap.
            visibilityLock.unlock();
        }
    }
    ...
}

很长时间以后,visibilityLock.lock()块内将多次调用tryfinally块中的代码记录了此情况,当发生这种情况时,我确实看到了日志消息。我的重映射函数主要是幂等的,因此,visibilityLock.lock()除外,多次调用它是无害的。如果是finally块,则根据需要通过多次解锁来处理它。

visibilityLock是从ReentrantReadWriteLock获得的读锁定。此另一把锁的目的是确保在updateFunction返回之前,该锁之外的另一数据结构看不到compute所做的更改。

在我们对非问题进行旁听之前,我已经知道ConcurrentMap.compute的默认实现指示重新映射函数可以多次调用。但是,ConcurrentHashMap中的覆盖(和相应的文档)提供了原子性的保证,并且实现表明这是正确的(确实)。

还有其他人遇到此问题吗?是JDK错误,还是我做错了什么?

我正在使用:

$ java -version
openjdk version "1.8.0_181"
OpenJDK Runtime Environment (build 1.8.0_181-b13)
OpenJDK 64-Bit Server VM (build 25.181-b13, mixed mode)

1 个答案:

答案 0 :(得分:0)

眼球JDK代码:

很明显,计算不会两次调用remappingFunction。剩下三种可能性:

  • 您没有ConcurrentHashMap,但还有其他东西。在运行时检查具体类型并将其转储到日志文件中。
  • 您两次致电compute。函数中是否有任何流程控制未按预期执行?您已删除了大部分功能,所以我无法说。
  • 您一次致电compute,但是您的remappingFunction锁定了两次。 lambda中是否有任何流量控制可能未按照您的想法进行操作?同样,您已删除了大部分功能,因此我无能为力。

要调试,请在锁定时检查锁定计数,如果锁定计数非零,则将堆栈转储到日志文件中。