Как атомарная переменная обеспечивает безопасность в многопоточности
Что проверяют
Отличаете ли вы атомарность от видимости и понимаете ли, на чём держится AtomicInteger. Следом обычно спрашивают, чем он лучше @Volatile и synchronized и что использовать в корутинах.
Как отвечать
Гонка возникает потому, что count++ это три шага: прочитать, прибавить, записать. Два потока читают одно значение, и одно увеличение теряется. Классы из java.util.concurrent.atomic (AtomicInteger, AtomicLong, AtomicBoolean, AtomicReference) решают это без блокировок:
- CAS:
compareAndSet(expected, new)записывает новое значение, только если текущее всё ещё равно ожидаемому. Атомарность обеспечивает процессор: на ARM64 это пара эксклюзивных инструкций load/store или одна инструкция из расширения LSE; - цикл повтора:
incrementAndGet()иupdateAndGet { }читают значение, считают новое и пробуют CAS. Не вышло, значит кто-то успел раньше, и попытка повторяется; - видимость:
get()иset()имеют семантикуvolatile, запись в одном потоке гарантированно видна в другом (отношение happens-before).
Сравнение с альтернативами. @Volatile var count даёт только видимость, count++ на нём всё равно гонка. synchronized или Mutex из корутин нужны, когда меняется несколько связанных полей. Для счётчика под высокой конкуренцией лучше LongAdder: он раскладывает значение по ячейкам.
В Android-коде CAS встречается чаще, чем кажется: MutableStateFlow.update { } устроен ровно так, и это правильный способ менять состояние во ViewModel из разных корутин. Для Kotlin Multiplatform есть kotlinx.atomicfu.
Где спотыкаются
Пишут if (!flag.get()) flag.set(true). Каждая операция атомарна, а пара нет: между проверкой и записью успеет другой поток. Нужен compareAndSet(false, true).
Считают, что две атомарные переменные вместе дают атомарное обновление. Инвариант между ними так не защитить, нужна блокировка или один AtomicReference на неизменяемый объект.
Кладут побочные эффекты в лямбду updateAndGet { } или update { }. При конфликте она вызывается повторно, и запрос в сеть или запись в лог выполнится дважды.