В чём заключаются различия между 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.