Atomic типы данных

Классы java.util.concurrent.atomic выполняют одиночные обновления общего состояния атомарно. Они полезны для счётчиков, флагов и ссылок, когда инвариант умещается в одну переменную и блокировка не нужна.

Основные классы

ГруппаПримеры
скалярыAtomicInteger, AtomicLong, AtomicBoolean
ссылкиAtomicReference, AtomicStampedReference
массивыAtomicIntegerArray, AtomicReferenceArray
поляAtomicIntegerFieldUpdater
конкурентные счётчикиLongAdder, LongAccumulator

Стандартного AtomicDouble нет. Значение double можно хранить как биты в AtomicLong, обновлять через AtomicReference<Double> или применять DoubleAdder, если нужен конкурентный накопитель без атомарного снимка каждой операции.

Compare-and-set

CAS записывает новое значение, только если текущее равно ожидаемому.

AtomicInteger balance = new AtomicInteger(10);
 
int updated = balance.updateAndGet(current -> {
    if (current == 0) throw new IllegalStateException("empty");
    return current - 1;
});

Упрощённо обновление повторяется в цикле:

int current;
int next;
do {
    current = balance.get();
    next = current - 1;
} while (!balance.compareAndSet(current, next));

При конкуренции CAS может несколько раз проиграть и повторить вычисление. Поэтому функция в updateAndGet не должна выполнять внешний необратимый эффект: она может быть вызвана больше одного раза.

Видимость и ограничения

Операции get/set атомарных классов имеют volatile-подобные гарантии видимости. Подробная связь описана в happens before.

Атомарная переменная не защищает инвариант из нескольких значений: проверка двух счетов и перевод между ними требуют lock, immutable state в одном AtomicReference или другой координации.

ABA-проблема: значение меняется A → B → A, и обычный CAS не замечает промежуточного состояния. Версию можно хранить в AtomicStampedReference.

LongAdder снижает конкуренцию, распределяя обновления по ячейкам. sum() не является атомарным снимком относительно одновременных записей.

Вопросы для собеседования

  1. Почему i++ не атомарен? Это чтение, вычисление и запись.
  2. Что делает CAS? Условно заменяет значение при совпадении с ожидаемым.
  3. Есть ли AtomicDouble? Нет; выбор зависит от требуемой семантики.
  4. Когда LongAdder лучше AtomicLong? При частых конкурентных прибавлениях и редком чтении точной суммы.
  5. Что такое ABA? Возврат значения к A скрывает промежуточное изменение.
  6. Может ли atomic заменить транзакцию нескольких полей? Нет, если поля не объединены в одно атомарно заменяемое состояние.
  7. Почему updater-функция должна быть без побочных эффектов? CAS-цикл может выполнить её повторно.