我正在开发一个高度并发的应用程序,该应用程序使用基于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()
块内将多次调用try
。 finally
块中的代码记录了此情况,当发生这种情况时,我确实看到了日志消息。我的重映射函数主要是幂等的,因此,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)
答案 0 :(得分:0)
眼球JDK代码:
很明显,计算不会两次调用remappingFunction
。剩下三种可能性:
ConcurrentHashMap
,但还有其他东西。在运行时检查具体类型并将其转储到日志文件中。compute
。函数中是否有任何流程控制未按预期执行?您已删除了大部分功能,所以我无法说。compute
,但是您的remappingFunction
锁定了两次。 lambda中是否有任何流量控制可能未按照您的想法进行操作?同样,您已删除了大部分功能,因此我无能为力。要调试,请在锁定时检查锁定计数,如果锁定计数非零,则将堆栈转储到日志文件中。