Получить доступ

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

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

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

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

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

Так подсказку видит пользователь sotto на живом собеседовании — за пару секунд, пока вы думаете вслух.

Подробно

Атомарная операция выполняется целиком или не выполняется: другой поток не увидит промежуточного состояния. Обычные чтение и запись выровненного слова атомарны, а 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 всегда быстрее блокировок.

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

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

Закрытая бета

Подсказки на собеседовании — в реальном времени

sotto слышит вопрос интервьюера и показывает короткий ответ и план, как в блоке «Коротко». Оставьте контакт — пришлём доступ и промокод.

  • Промокод на 2 часа — сверх пробных 30 минут
  • Программа для Windows — скачать сразу, войти — через Яндекс ID или VK ID
  • Прямая связь с разработчиком: что улучшить — сделаем