ConcurrentHashMap только для чтения/обновления с помощью метода вычисления

По сути, это продолжение этого или это или многие другие в этом отношении.

Мой вопрос, вероятно, прост: если я использую ConcurrentHashMap::compute только, как для чтения, так и для записи, достаточно ли этого для обеспечения видимости?

Я знаю этот compute метод:

Весь вызов метода выполняется атомарно

Достаточно ли этого, чтобы гарантировать видимость? В частности, является ли эта истинная спецификация/документация мудрой в отношении happens-before? Чтобы упростить мой вопрос, вот пример:

static class State {
    private int age;

    int getAge() {
        return age;
    }

    State setAge(int age) {
        this.age = age;
        return this;
    }
} 

а также :

// very simplified, no checks
static class Holder {

    private static final ConcurrentHashMap<String, State> CHM = new ConcurrentHashMap<>();

    public State read(String key) {
        return CHM.compute(key, (x, y) -> y);
    }

    public State write(String key, int age) {
        return CHM.compute(key, (x, y) -> y.setAge(y.getAge() + age));
    }
}

Никто не имеет доступа к CHM и может работать только через Holder.

Для меня ответ, очевидно, да, это безопасно, и все читатели увидят результат последнего метода write. Я просто не могу соединить точки с документацией ConcurrentHashMap, что, скорее всего, очевидно, но я, кажется, упустил это.


person Eugene    schedule 03.06.2021    source источник
comment
Я бы посчитал, что это подразумевается гарантией потокобезопасности.   -  person Louis Wasserman    schedule 03.06.2021
comment
Вы возвращаетесь к проблеме, которая уже объяснена здесь. Проблема в том, что делает вызывающий read? Он увидит «результат последней операции write», но какая запись была последней? Без какого-либо определенного порядка ничего еще может не произойти, и все они могут выполняться прямо сейчас, одновременно с вызывающей стороной метода read.   -  person Holger    schedule 04.06.2021
comment
Да, для наглядности вполне достаточно. Я думаю, что это слишком много для видимости. Для метода read() вы можете использовать CHM.get() и жить долго и счастливо.   -  person Tamas Rev    schedule 04.06.2021
comment
@TamasRev нет, ты не можешь использовать get. Комментарии и первоначальные вопросы, на которые я ссылался, обсуждают это гораздо шире.   -  person Eugene    schedule 04.06.2021
comment
@Holger, ты не против опубликовать ответ? Я понял твою мысль наконец.   -  person Eugene    schedule 09.06.2021


Ответы (1)


compute() javadoc говорит, что делает этот метод:

Пытается вычислить сопоставление для указанного ключа и его текущего сопоставленного значения (или null, если текущее сопоставление отсутствует).

Итак, compute() заменяет значение ключа.

Использование compute() для изменения внутренних полей какого-либо объекта (даже объект хранится как значение в карте) не является тем, для чего предназначался compute().
Поэтому, естественно, спецификация/документация compute() гарантирует (и даже говорит) ничего об этом.

Что касается happens-before, в документации есть несколько упоминаний:

Важно то, что отношение happen-before гарантируется только между вставкой/удалением/извлечением объектов в/из коллекции.
В вашем случае это тот же объект State (обновляются только внутренние его поля), поэтому согласно IMO к документации ConcurrentHashMap даже позволено решить, что ничего не изменилось и пропустить оставшиеся этапы синхронизации.

person pluk    schedule 04.06.2021
comment
Я не знаю, где вы решили в первом абзаце, что он собирается заменить значение и что никакие мутации не допускаются, что все еще заменяет, если вы спросите меня. Тогда простое выделение слов происходит до — это далеко не то, что мне нужно. Я не думаю, что это отвечает на мой вопрос. Возможно, я изменю свое мнение через некоторое время, когда перечитаю его, но в настоящее время я не могу с этим согласиться. - person Eugene; 08.06.2021