Активация Java ConcurrentHashMap

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

ConcurrentHashMap<String, Integer> counts = new ...;

private void countThing(String thing) {
    while (true) {
        Integer currentCount = counts.get(thing);
        if (currentCount == null) {
            if (counts.putIfAbsent(thing, 1) == null)
                break;
        } else if (counts.replace(thing, currentCount, currentCount + 1)) {
            break;
        }
    }
}

С моей точки зрения (concurrency новички) поток t1 и поток t2 могут читать currentCount = 1. Тогда оба потока могут изменить значение карты на 2. Может ли кто-нибудь объяснить мне, подходит ли код или нет?

Ответ 1

Фокус в том, что replace(K key, V oldValue, V newValue) обеспечивает атомарность для вас. Из документов (внимание мое):

Заменяет запись для ключа только, если в настоящее время отображается на заданное значение.... действие выполняется атомарно.

Ключевое слово "атомарно". Внутри replace "проверьте, является ли старое значение ожидаемым, и только если оно есть, замените его" происходит как один кусок работы, без каких-либо других потоков, способных чередоваться с ним. Это для реализации, чтобы выполнить любую синхронизацию, необходимую для обеспечения этой атомарности.

Таким образом, не может быть, что оба потока видят currentAction == 1 из функции replace. Один из них увидит его как 1, и поэтому его обращение к replace вернет true. Другой будет видеть его как 2 (из-за первого вызова) и, таким образом, вернуть false — и повторите попытку, чтобы повторить попытку, на этот раз с новым значением currentAction == 2.

Конечно, может быть, что третий поток обновил currentAction до 3 в то время, и в этом случае этот второй поток будет просто продолжать попытки, пока ему не повезет, чтобы никто не прыгнул впереди него.

Ответ 2

Может кто-нибудь объяснить мне, если код в порядке или нет?

В дополнение к ответу yshavit вы можете не писать собственный цикл, используя compute, который был добавлен в Java 8.

ConcurrentMap<String, Integer> counts = new ...;

private void countThing(String thing) {
    counts.compute(thing, (k, prev) -> prev == null ? 1 : 1 + prev);
}

Ответ 3

С помощью вы можете также заменить значение.

if (currentCount == null) {
        counts.put(thing, 2);
    }