Зачем нужны методы incrementAndGet и decrementAndGet в AtomicInteger
Что проверяют
Понимаете ли вы, почему обычный count++ небезопасен в многопоточном коде и как атомики решают это без блокировок. Следом спрашивают, чем incrementAndGet отличается от getAndIncrement и когда атомика уже не хватает.
Как отвечать
- проблема:
count++это три шага: прочитать, прибавить, записать. Два потока читают одно значение, и один инкремент теряется.volatileдаёт видимость, но не атомарность; - решение:
AtomicIntegerизjava.util.concurrent.atomicвыполняет чтение, изменение и запись одной неделимой операцией на уровне процессора (сравнение с обменом, CAS, или атомарное сложение). Потоки не блокируются, а при конфликте повторяют попытку; - возвращаемое значение:
incrementAndGetиdecrementAndGetвозвращают новое значение, как++i.getAndIncrementвозвращает старое, какi++. Это важно, когда результат служит идентификатором или его сравнивают с нулём; - видимость: у операций атомиков семантика
volatile, запись в одном потоке видна другим.
Примеры из Android: счётчик активных загрузок, по которому прячут индикатор, когда decrementAndGet() вернул ноль; уникальные ID уведомлений; счётчик ссылок на общий ресурс; ожидание фоновой работы в Espresso через CountingIdlingResource.
Составные обновления делают через updateAndGet или accumulateAndGet (с API 24) либо циклом с compareAndSet. При сильной конкуренции за счётчик лучше подходит LongAdder. В корутинах атомик тоже работает, но для состояния удобнее MutableStateFlow.update {}, а для критической секции Mutex.
Где спотыкаются
Считают, что volatile int с ++ уже потокобезопасен. Он только гарантирует, что поток увидит свежее значение.
Склеивают две атомарные операции в проверку: if (c.get() < max) c.incrementAndGet(). Между вызовами вклинивается другой поток, и лимит превышается. Нужен compareAndSet в цикле или updateAndGet.
Говорят, что атомик всегда быстрее synchronized. При высокой конкуренции CAS крутится в повторах, а если несколько полей нужно менять согласованно, атомик на одно поле не поможет, нужна блокировка.