Получить доступ

volatile, атомарность и CAS

Разница видимости и атомарности, atomic-классы, compare-and-swap и ABA, контеншн и LongAdder.

volatile решает не всё

volatile даёт видимость и запрет переупорядочивания (детали — в уроке про JMM), но НЕ делает составные операции атомарными. Классический пример: volatile int n; n++ — это read-modify-write из трёх шагов, и два потока легко затрут инкремент друг друга.

Идеальные кейсы volatile — одиночная запись/чтение флага или ссылки (флаг остановки, публикация готового объекта), а также volatile-поле в double-checked locking.

Атомарные классы

Для атомарных счётчиков и ссылок без блокировок есть пакет java.util.concurrent.atomic: AtomicInteger, AtomicLong, AtomicReference, AtomicBoolean. Их операции (incrementAndGet, getAndAdd, compareAndSet, updateAndGet) атомарны и потокобезопасны.

Это часто дешевле synchronized для простых операций, потому что не блокирует поток, а повторяет попытку.

AtomicInteger counter = new AtomicInteger();
counter.incrementAndGet();              // атомарно, без блокировки
counter.updateAndGet(v -> Math.min(v + 1, MAX));

CAS изнутри

Atomic-классы построены на CAS (compare-and-swap) — атомарной инструкции процессора «если значение равно ожидаемому, заменить на новое, иначе ничего». Алгоритм оптимистичен: прочитать текущее, вычислить новое, попытаться CAS; при неудаче (кто-то опередил) повторить цикл.

Так строят lock-free структуры: прогресс есть всегда без блокировок и без deadlock. Минусы — busy-retry под высокой конкуренцией и ABA-проблема: значение успело смениться A→B→A, CAS этого не заметит. Где это важно, используют AtomicStampedReference (значение + версия).

Контеншн и LongAdder

При высокой конкуренции один CAS-счётчик становится узким местом: потоки постоянно мешают друг другу и крутят retry-цикл, плюс кэш-линия с переменной «пинается» между ядрами (cache-line contention).

LongAdder решает это, разбивая счётчик на несколько ячеек (по потокам/полосам): потоки инкрементируют разные ячейки, а sum() складывает их. Для счётчиков с интенсивной записью LongAdder заметно быстрее AtomicLong; если же чаще читают точное значение — AtomicLong проще.

Как отвечать на собеседовании

Чётко разделите видимость (volatile) и атомарность (n++ небезопасно даже с volatile). Объясните CAS как оптимистичный lock-free цикл, назовите ABA и AtomicStampedReference, и скажите, когда брать LongAdder вместо AtomicLong (write-heavy счётчики, cache-line contention).

Вопросы по теме

Читайте также

Закрытая бета

Знать тему — половина дела

На живом собеседовании sotto слышит вопрос интервьюера и показывает короткий ответ и план. Оставьте контакт — пришлём доступ и промокод.

  • Промокод на 2 часа — сверх пробных 30 минут
  • Программа для Windows — скачать сразу, войти — через Яндекс ID или VK ID
  • Прямая связь с разработчиком: что улучшить — сделаем