JDK 8: иногда кажется, что ConcurrentHashMap.compute разрешает множественные вызовы для переназначения функции - PullRequest
0 голосов
/ 08 июля 2019

Я работаю над приложением с высокой степенью параллелизма, которое использует объектный кеш на основе 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)

1 Ответ

0 голосов
/ 09 июля 2019

Набирает взгляд код JDK:

Очевидно, что вычисления не вызовут remappingFunction дважды.Это оставляет три возможности:

  • У вас нет ConcurrentHashMap, но что-то еще.Проверьте конкретный тип во время выполнения и выведите его в файл журнала.
  • Вы дважды звоните compute.Есть ли в функции управление потоком, которое может не выполнять то, что вы ожидаете?Вы удалили большую часть функции, поэтому я не могу сказать.
  • Вы звоните compute один раз, но ваш remappingFunction блокируется дважды.Есть ли в лямбде какой-нибудь регулятор потока, который может не делать то, что вы думаете?Опять же, вы удалили большую часть функции, поэтому я ничего не могу сделать, чтобы помочь.

Для отладки проверьте количество блокировок в точке блокировки, и, если оно ненулевое, выведите дампстек в лог-файл.

...