Считаются ли computeIfPresent и computeIfAbsent при использовании одна за другой операциями атома c? - PullRequest
0 голосов
/ 04 мая 2020

С учетом следующего класса, если несколько потоков одновременно выполняют метод testComputeIfPresentAndAbsent, безопасен ли поток кода? :

public class ComputeIfPresentAndAbsent {

    private ConcurrentHashMap<String, MyPojo> map = new ConcurrentHashMap<>();

    private void testComputeIfPresentAndAbsent(String key, MyPojo newObj) {
        map.computeIfPresent(key, (k, existingObj) -> aggregate(existingObj, newObj));//Line 1
        map.computeIfAbsent(key, k -> newObj);//Line 2
    }

    private MyPojo aggregate(MyPojo existingObj, MyPojo newPojo) {
        newPojo.getField1().add(existingObj.getField1());
        newPojo.getField2().add(existingObj.getField2());
        return newPojo;
    }

    class MyPojo {

        private BigDecimal field1;
        private BigDecimal field2;

        public BigDecimal getField1() {
            return field1;
        }

        public void setField1(BigDecimal field1) {
            this.field1 = field1;
        }

        public BigDecimal getField2() {
            return field2;
        }

        public void setField2(BigDecimal field2) {
            this.field2 = field2;
        }

    }
}

Другими словами, вызовы computeIfPresent и computeIfAbsent один за другим будут операцией атома c или существует возможность возникновения условия гонки в этом сценарии?

Если бы мне пришлось упростить вопрос, рассмотрим следующую хронологию событий:

  1. Поток A выполняет строку 1 (computeIfPresent) для ключа 1. Поскольку ключ 1 отсутствует , aggregate функция не вызывается для ключа 1 потоком A.
  2. Поток A выполняет строку 2 (computeIfAbsent) для ключа 1 и находится в процессе добавления объекта к ключу 1. В то же время В это время поток B входит и выполняет строку 1 (computeIfPresent) для ключа 1.

Вопрос: Будет ли поток B ждать в строке 1, пока поток A не завершит выполнение строки 2 (computeIfAbsent) и только потом выполнять функцию aggregate? Или поток B сразу перейдет к строке 2, не дожидаясь строки 1? (Предполагая, что оба потока работают с одним и тем же ключом)

Насколько я понимаю, поток B не будет ждать в строке 1, пока поток A выполняет строку 2 для одного и того же ключа. Это понимание правильно? Если да, то этот код не является потокобезопасным, так как несколько потоков могут пропустить вызов агрегатного метода вообще. Даже если бы мне удалось доказать эту теорию с помощью некоторого примера программы, вызывающей метод testComputeIfPresentAndAbsent в 10000 потоков, меня прежде всего интересует понимание того, почему этот код не является потокобезопасным и правильное ли мое понимание?

...