Как гарантировать get () ConcurrentHashMap, чтобы всегда возвращать последнее фактическое значение?

введение
Предположим, у меня есть ConcurrentHashMap singleton:

public class RecordsMapSingleton {

    private static final ConcurrentHashMap<String,Record> payments = new ConcurrentHashMap<>();

    public static ConcurrentHashMap<String, Record> getInstance() {
        return payments;
    }

}

тогда у меня три в последующем запросы (все обрабатываются разными потоками) из разных источников.
Первая служба делает запрос, который получает синглтон, создает Record например, генерирует уникальный идентификатор и помещает его в Map, затем отправляет этот код в другой сервис.
Затем Вторая служба делает другую запрос, с этим удостоверением. Он получает синглтон, находит Record экземпляр и изменяет его.
Наконец (вероятно, через полчаса) Вторая служба делает еще один запрос, чтобы изменить Record далее.


В некоторых действительно редких случаях я испытываю плавающая ошибка. В логах я вижу, что первый запрос успешно размещен Record на Map, второй запрос нашел его по ID и изменил его, а затем третий запрос попытался найти запись по ID, но ничего не нашел (get() вернулся null).
Единственное, что я нашел о ConcurrentHashMap гарантии, составляет:

действия в потоке перед помещением объекта в любой параллельный collection happen-перед действиями, последующими за доступом или удалением этого элемента из коллекции в другом потоке.

С здесь. Если я правильно понял, это буквально означает, что get() может вернуть любое значение, которое на самом деле было когда-то на карте, насколько это не разрушает happens-before отношения между действиями в разных потоках.
В моем случае это относится так: если третий запрос не заботится о том, что произошло во время обработки первого и второго, то он может прочитать null С Map.

Это не подходит мне, потому что мне действительно нужно получить от Map последние актуальные Record.

что я пробовал
Поэтому я начал думать, как сформировать happens-before отношения между последующими Map модификации; и пришел с идеей. JLS говорит (в 17.4.4), что:

запись в изменчивую переменную v (§8.3.1.4) синхронизируется-со всеми последующие чтения v любым потоком (где" последующий " определяется согласно порядку синхронизации).

так, предположим, я модифицирую свой синглтон следующим образом:

public class RecordsMapSingleton {

    private static final ConcurrentHashMap<String,Record> payments = new ConcurrentHashMap<>();
    private static volatile long revision = 0;

    public static ConcurrentHashMap<String, Record> getInstance() {
        return payments;
    }

    public static void incrementRevision() {
        revision++;
    }
    public static long getRevision() {
        return revision;
    }

}

затем, после каждой модификации Map или Record внутри, я позвоню incrementRevision() и перед любым чтением с карты я позвоню getRevision().

вопрос
Из-за природы heisenbugs никакое количество тестов не достаточно, чтобы сказать, что это решение правильно. И я не эксперт в параллелизме, поэтому не мог проверить это формально.

Может кто-то одобрить, что следуя этому подходу, я всегда буду получать последнее фактическое значение от ConcurrentHashMap? Если этот подход неверен или кажется неэффективным, не могли бы вы порекомендовать мне что-то еще?

1 ответов


вы подходите не будет работать, как вы на самом деле повторяя ту же ошибку снова. As ConcurrentHashMap.put и ConcurrentHashMap.get создать бывает перед отношения, но не время заказа гарантии, точно так же относится к вашим читает и пишет в volatile переменной. Они образуют бывает перед отношения, но не время заказа гарантии, если один поток случается вызвать get перед тем, как другой выполнил put, то же самое относится к volatile читать, что будет произойдет до volatile писать тут. Кроме того, вы добавляете еще одну ошибку, применяя ++ оператора volatile переменная не является атомарной.

гарантии, сделанные для volatile переменные не сильнее, чем эти, сделанные для ConcurrentHashMap. это документация прямо говорится:

извлечения отражают результаты самых последних завершено операции обновления на их начало.

в JLS говорится что внешние действия являются действиями между потоками относительно программы:

действие между потоками-это действие, выполняемое одним потоком, которое может быть обнаружено или непосредственно подвержено влиянию другого потока. Существует несколько видов действий между потоками, которые может выполнять программа:

...

  • Внешние Действия. Внешний действие-это действие, которое может наблюдаться за пределами выполнения и имеет результат, основанный на внешней среде выполнения.

просто сказал, Если один поток помещает в ConcurrentHashMap и отправляет сообщение внешнему объекту, а второй поток получает от того же ConcurrentHashMap после получения сообщения от внешнего объекта в зависимости от ранее отправленного сообщения не может быть проблемы видимости памяти.

это может быть так, что эти действия не запрограммированы таким образом или что внешний объект не имеет предполагаемой зависимости, но может быть так, что ошибка лежит в совершенно другой области, но мы не можем сказать, поскольку вы не разместили соответствующий код, например, ключ не соответствует или код печати неверен. Но что бы это ни было, это не будет исправлено volatile переменной.