Какие проблемы решает atomic
Что проверяют
Понимаете ли вы, почему count++ из двух потоков теряет обновления и чем атомарный класс отличается от volatile и synchronized. Следом обычно спрашивают, как это работает внутри и где atomic уже не спасает.
Как отвечать
Начните с проблемы. count++ это три шага: прочитать, прибавить, записать. Два потока читают одно значение, оба пишут +1, одно обновление пропадает. Классы из java.util.concurrent.atomic решают две вещи:
- неделимость чтения-изменения-записи:
incrementAndGet(),getAndAdd(),compareAndSet()выполняются как одна операция; - видимость: запись в атомик видна другим потокам так же, как запись в
volatile, без устаревших значений.
Внутри нет блокировки, используется инструкция процессора compare-and-swap. Поток читает значение, считает новое и записывает его, только если старое не изменилось, иначе повторяет. Поток не засыпает на мониторе, и при низкой конкуренции это дешевле synchronized.
Примеры на Android:
- флаг «выполнить один раз»:
AtomicBoolean.compareAndSet(false, true)для инициализации SDK или защиты от двойного нажатия, когда событие может прийти из разных потоков; - счётчики и id:
AtomicIntegerилиAtomicLong, а при очень частых инкрементах из многих потоковLongAdder; - ссылка на состояние:
AtomicReferenceсupdateAndGet { }. Лямбда может выполниться несколько раз, поэтому побочных эффектов в ней быть не должно.
В корутинах та же логика у MutableStateFlow.update { }: внутри цикл compare-and-set, а не простое присваивание.
Где спотыкаются
Говорят, что atomic «блокирует переменную для других потоков». Блокировки там нет, есть оптимистичная запись с повтором.
Считают, что volatile решает гонку в count++. Он даёт только видимость, неделимости операции нет.
Пытаются связать атомиками два поля с общим инвариантом, например баланс и историю операций. Каждое изменение атомарно само по себе, а пара нет: тут нужна блокировка, Mutex в корутинах или одно неизменяемое состояние внутри AtomicReference.