Вопрос с собеседования · Java

Два потока одновременно вызывают incrementAndGet() у одного AtomicInteger. Что при этом происходит внутри и почему ни один инкремент не теряется?

MiddleМногопоточность
Коротко

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?

Разбор темы

Связанные вопросы

Партнёр платформы

Разбор собеседования с sotto

После собеседования sotto разбирает разговор: оценка, каждый вопрос — хорошо, частично или слабо, чего не хватило в ответе и над чем поработать к следующему разу. Первые 30 минут бесплатно.

Разбор собеседования