Два потока одновременно вызывают incrementAndGet() у одного AtomicInteger. Что при этом происходит внутри и почему ни один инкремент не теряется?
CAS-цикл: прочитать значение, вычислить новое и записать, только если в памяти всё ещё старое (одна атомарная инструкция процессора). Проиграл гонку — повторить с новым значением. Блокировок нет, поток не засыпает.
- Из двух одновременных CAS успешен ровно один
- На x86 — инструкция lock cmpxchg
- При сильной конкуренции растёт число повторов — LongAdder
А ещё Lock-free: система в целом прогрессирует, даже если отдельный поток задержан
Подробно
Начни с механизма: incrementAndGet не блокирует ничего, а крутит CAS-цикл. Поток читает текущее значение (пусть 5), вычисляет новое (6) и вызывает аппаратную инструкцию compare-and-swap: «если в памяти всё ещё 5 — положи 6, иначе не трогай». Инструкция атомарна на уровне процессора, поэтому из двух потоков успевает ровно один; проигравший видит, что CAS не прошёл, перечитывает уже 6, вычисляет 7 и повторяет попытку. Ни один инкремент не теряется именно потому, что запись условная — она привязана к тому значению, которое поток видел. Дальше назови свойство: это lock-free — поток не засыпает на мониторе, deadlock невозможен, но и гарантии для конкретного потока нет, он может проигрывать гонку несколько раз подряд. Закончи ценой: при сильной конкуренции цикл повторов и «пинг-понг» кэш-линии между ядрами делают один общий счётчик узким местом — тогда берут LongAdder, который разносит счёт по ячейкам. И упомяни ABA: CAS сравнивает значение, а не историю, поэтому A→B→A он не заметит; где это важно, нужен AtomicStampedReference.
Что хотят услышать
- Механика CAS и цикла повторов.
- Почему ни один инкремент не теряется.
- Ограничения под высокой конкуренцией.
Частые ошибки
- Считать, что atomic-классы «внутри синхронизированы» — поток не блокируется, а повторяет попытку.
- Обещать, что CAS всегда быстрее synchronized: под высокой конкуренцией цикл повторов проигрывает.
Проверьте себя
Один верный вариант. Подумайте, потом откройте ответ.
Показать варианты и ответ
- ✗Метод захватывает внутренний замок, второй поток засыпает до его освобождения
- ✓CAS-цикл: запись проходит, только если значение не изменилось, иначе повтор
- ✗Оба потока пишут одновременно, и JVM потом складывает их результаты
- ✗Значение кэшируется в каждом потоке и синхронизируется с памятью при следующем вызове get
Что спросят дальше
- Что такое ABA-проблема и чем помогает AtomicStampedReference?
- Почему при высокой конкуренции LongAdder обгоняет AtomicLong?
Разбор темы
- volatile, атомарность и CAS
Разница видимости и атомарности, atomic-классы, compare-and-swap и ABA, контеншн и LongAdder.
Связанные вопросы
Разбор собеседования с sotto
После собеседования sotto разбирает разговор: оценка, каждый вопрос — хорошо, частично или слабо, чего не хватило в ответе и над чем поработать к следующему разу. Первые 30 минут бесплатно.