Я работаю над приложением с высокой степенью параллелизма, которое использует объектный кеш на основе ConcurrentHashMap
.Мое понимание ConcurrentHashMap
заключается в том, что вызовы семейства методов «compute» гарантируют атомарность по отношению к функции переназначения.Однако я обнаружил, что это выглядит как аномальное поведение: иногда функция переназначения вызывается более одного раза.
Следующий фрагмент в моем коде показывает, как это может произойти и что мне нужно сделать, чтобы обойти это.it:
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)