Gymterview
senior

В чём заключаются различия между java.util.concurrent.Atomic*.compareAndSwap() и java.util.concurrent.Atomic*.weakCompareAndSwap()

Начиная с Java 9, методы были переименованы: compareAndSet() (сильная версия) и weakCompareAndSetPlain() / weakCompareAndSetAcquire() / weakCompareAndSetRelease() / weakCompareAndSetVolatile() (слабые версии с разной семантикой упорядочивания памяти).

Основные различия

Характеристика compareAndSet() (сильный CAS) weakCompareAndSet*() (слабый CAS)
Гарантия успеха Если текущее значение совпадает с ожидаемым – всегда обновит Может спонтанно вернуть false даже при совпадении значений
Memory barrier Создаёт полный барьер памяти, обеспечивает happens-before В зависимости от варианта: Plain – без барьера, Acquire/Release/Volatile – с соответствующим барьером
Производительность Чуть дороже из-за барьеров и гарантий На некоторых архитектурах (ARM, PowerPC) может быть быстрее
Применение Универсальное: одиночные CAS-операции Только внутри циклов повтора, где спонтанный false не страшен

Почему слабый CAS может вернуть false без причины

На архитектурах с LL/SC (Load-Linked / Store-Conditional), таких как ARM и RISC-V, CAS реализуется через пару инструкций. Между LL и SC может произойти «ложная инвалидация» кэш-линии (например, из-за переключения контекста или записи в соседнюю переменную той же кэш-линии), и SC завершится неудачей, хотя значение не менялось. Сильный CAS компенсирует это циклом повтора внутри себя; слабый – нет.

Когда использовать слабый CAS

Слабый CAS оправдан, когда:

  • операция уже находится в цикле повтора (retry loop), и дополнительная итерация обходится дёшево;
  • не нужна полная семантика happens-before (используется weakCompareAndSetPlain());
  • критична максимальная производительность на архитектурах LL/SC.
Пример: использование weakCompareAndSetVolatile() в цикле
// Потокобезопасный инкремент с использованием слабого CAS
// (иллюстрация -- для реальных счётчиков используйте AtomicInteger.incrementAndGet())
public static int weakIncrement(AtomicInteger counter) {
    int current;
    do {
        current = counter.get();
    } while (!counter.weakCompareAndSetVolatile(current, current + 1));
    // Спонтанный false просто приведёт к ещё одной итерации цикла
    return current + 1;
}

Аналогия из жизни. Представьте автомат по продаже напитков. Сильный CAS – это надёжный автомат: если монета вставлена правильно, напиток всегда выдаётся. Слабый CAS – это капризный автомат: иногда он возвращает монету без причины, и нужно вставить её повторно. Если вы уже стоите у автомата и готовы повторить – капризный автомат не проблема, зато он может работать чуть быстрее.

На собеседовании. Этот вопрос проверяет глубину знания низкоуровневых механизмов. Если вас просят сравнить – обязательно упомяните: (1) спонтанный false у слабого CAS, (2) различия в memory barriers, (3) зависимость от архитектуры процессора (LL/SC vs x86). На x86 разницы в производительности практически нет, потому что CAS реализуется одной инструкцией CMPXCHG.