Как работает AtomicInteger и почему он потокобезопасен
Что проверяют
Знаете ли вы про сравнение с обменом (CAS) и понимаете ли, почему это работает без блокировок.
Как отвечать
Проблема в том, что count++ это три операции: прочитать, увеличить, записать. Два потока могут прочитать одно значение и записать одинаковый результат, потеряв инкремент.
AtomicInteger решает это без блокировок, через инструкцию процессора compare-and-swap. Алгоритм такой: прочитать текущее значение, вычислить новое, атомарно записать при условии, что текущее не изменилось. Если изменилось, повторить попытку.
public final int incrementAndGet() {
int prev, next;
do { prev = get(); next = prev + 1; }
while (!compareAndSet(prev, next));
return next;
}Само поле объявлено volatile, поэтому обеспечивается и видимость: изменение сразу видно другим потокам.
Почему это лучше synchronized на счётчике: поток не засыпает и не будит других, нет переключения контекста. Такая синхронизация называется неблокирующей, а алгоритм свободным от блокировок.
Ограничения, которые стоит назвать: при высокой конкуренции растёт число повторов, и на многих ядрах AtomicInteger начинает проигрывать LongAdder, который раскладывает счётчик по ячейкам и складывает их только при чтении. И главное: атомарна одна операция, а не последовательность. Два вызова подряд снова дают гонку, для составных действий нужен updateAndGet или блокировка.
Где спотыкаются
Считают, что внутри синхронизация на мониторе.
Полагают, что несколько атомарных операций подряд тоже атомарны.
Не знают про LongAdder и упираются в конкуренцию.
Путают атомарность и видимость: volatile даёт только вторую.