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

Что такое атомарные операции и CAS?

MiddleКонкурентность
Ещё спрашивают так:
  • Как работает AtomicInteger?
  • Что такое compare-and-swap?
  • Что такое проблема ABA?
  • Что такое lock-free алгоритмы?
Коротко

CAS (compare-and-swap) — инструкция процессора «если в ячейке ещё A, запиши B» за один неделимый шаг. На ней строят атомарные счётчики и структуры без блокировок.

  • Счётчик на CAS: прочитать, посчитать, попробовать записать; не вышло — повторить
  • Нет засыпания потоков, но при сильной конкуренции много повторов
  • ABA: значение сменилось A → B → A, и CAS этого не заметил; лечится версией

А ещё Тот же принцип в базе — оптимистическая блокировка: UPDATE ... WHERE version = ?

Подробно

Атомарная операция выполняется целиком или не выполняется: другой поток не увидит промежуточного состояния. Обычные чтение и запись выровненного слова атомарны, а count++ — нет: это чтение, вычисление и запись.

CAS — аппаратная инструкция (lock cmpxchg на x86, LL/SC на ARM): CAS(addr, expected, new) атомарно сравнивает значение с ожидаемым и, если совпало, записывает новое; возвращает, удалось ли. Из неё строится любое атомарное обновление:

do {
    old = value
    new = f(old)
} while (!CAS(value, old, new))

Если между чтением и CAS другой поток изменил значение, CAS не пройдёт, и мы попробуем снова с новым значением. Никто не засыпает — это неблокирующий (lock-free) подход.

Где применяется: AtomicInteger, AtomicReference, ConcurrentHashMap (вставка в пустую корзину), очереди без блокировок, счётчики ссылок, реализация самих мьютексов (быстрый путь захвата свободного замка — один CAS).

Плюсы и минусы. Нет переключений контекста, нет дедлоков, поток, застрявший посреди операции, не держит остальных. Но при сильной конкуренции потоки крутятся в повторах и бьются за одну кэш-линию — производительность падает. Для горячих счетчиков в Java есть LongAdder: ячейки на поток, суммирование при чтении.

ABA. Поток прочитал A, отвлёкся; другой сменил A на B и обратно на A. CAS первого пройдёт, хотя состояние менялось — в lock-free стеке это может означать, что узел уже удалён и переиспользован. Решение — сравнивать пару (значение, версия): AtomicStampedReference, или сборщик мусора, который не даст переиспользовать узел, пока на него есть ссылка.

Тот же приём в распределённых системах: оптимистическая блокировка в базе (UPDATE ... SET balance = ?, version = version + 1 WHERE id = ? AND version = ? — если обновлено 0 строк, перечитать и повторить), условная запись в Redis (WATCH/MULTI), etcd и DynamoDB (conditional writes).

Пример

AtomicInteger counter = new AtomicInteger();
counter.incrementAndGet();                  // CAS-цикл внутри

AtomicReference<BigDecimal> balance = new AtomicReference<>(BigDecimal.ZERO);
balance.updateAndGet(b -> b.add(BigDecimal.TEN));   // f может вызваться несколько раз — без побочных эффектов!

Что хотят услышать

  • Определение атомарности и почему count++ не атомарен.
  • Как работает CAS и цикл повтора.
  • Плюсы против блокировок и поведение при высокой конкуренции.
  • ABA и версия; аналог в базе — оптимистическая блокировка.

Частые ошибки

  • Делать побочные эффекты внутри функции для updateAndGet — она может выполниться несколько раз.
  • Считать lock-free всегда быстрее блокировок.

Проверьте себя

Один верный вариант. Подумайте, потом откройте ответ.

Показать варианты и ответ
  • ✓CAS: «если в ячейке ещё A, запиши B» одной неделимой инструкцией; на ней строят атомарные счётчики без блокировок
  • ✗CAS захватывает ячейку памяти блокировкой ОС и усыпляет остальные потоки, пока владелец не запишет новое значение и не отпустит её
  • ✗CAS всегда замечает смену A → B → A
  • ✗При высокой конкуренции CAS никогда не повторяет попытки

Что спросят дальше

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

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

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

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

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