При неудаче CAS какой вывод делает поток о состоянии общего значения?
Неудачный CAS означает, что значение в общей переменной уже не равно ожидаемому: другой поток успел изменить его либо значение не совпадало изначально. Сам CAS при этом не выполняет запись, поэтому поток обычно перечитывает актуальное значение, пересчитывает новый результат и повторяет попытку.
CAS появился как аппаратная атомарная примитива для построения конкурентных алгоритмов без захвата общего блокировочного объекта. Такой подход снижает риск блокировок и позволяет отдельным операциям выполняться без явного synchronized.
В Java механизм доступен через классы пакета java.util.concurrent.atomic, например AtomicInteger и AtomicReference. Они предоставляют атомарные операции с согласованными гарантиями памяти, но не превращают произвольную последовательность действий над несколькими объектами в одну атомарную транзакцию.
Поток читает текущее значение, вычисляет на его основе новое и пытается записать результат. Между чтением и записью другой поток может изменить значение, поэтому без проверки запись способна затереть чужое обновление.
CAS решает проблему проверкой условия непосредственно в атомарной операции. Если условие не выполнено, использование устаревшего результата запрещается; ошибкой было бы просто продолжить работу с прежним ожидаемым значением.
Операция CAS принимает ожидаемое значение и новое значение. Она атомарно сравнивает фактическое значение с ожидаемым: при совпадении записывает новое значение и сообщает об успехе, при несовпадении ничего не записывает и сообщает о неудаче.
Типичный алгоритм строится как цикл: поток читает актуальное значение, вычисляет кандидат на новое состояние, выполняет CAS и повторяет весь цикл при неудаче. Повтор важен: после конфликта нужно использовать именно заново прочитанное состояние, иначе следующая попытка может снова опираться на устаревшие данные.
Здесь успешный CAS атомарно заменяет одну ссылку на другую. Неудача означает, что ссылка изменилась после чтения или уже не соответствовала ожидаемому объекту; новый объект не публикуется.
Операции атомарных классов обладают семантикой памяти, сопоставимой с доступом к volatile: успешная публикация и последующее чтение через этот механизм обеспечивают необходимую видимость. Однако CAS защищает только конкретную атомарную переменную; согласованность нескольких независимых переменных потребует блокировки, единого атомарного состояния или другой координации.
CAS не гарантирует, что каждый поток обязательно завершит цикл за ограниченное время. При высокой конкуренции возможны многочисленные неудачные попытки и голодание отдельных потоков. Для счётчиков при большом числе конкурирующих обновлений может подойти LongAdder, но его значение не является точным снимком в каждый момент времени и не заменяет CAS, когда нужна строгая атомарная проверка состояния.
В кэше нужно заменить конфигурацию только в том случае, если она всё ещё соответствует версии, которую поток прочитал перед валидацией. Вариант с synchronized прост и позволяет атомарно проверять и менять состояние, но создаёт конкуренцию за монитор и может блокировать независимые операции.
Вариант с AtomicReference хранит неизменяемую конфигурацию вместе с версией в одном объекте. Поток создаёт новый объект состояния и публикует его через CAS; если другой поток уже установил новую версию, CAS завершается неудачей, после чего проверка выполняется повторно для актуального состояния.
Выбран атомарный объект состояния, потому что операция естественно выражается как замена одной ссылки, а критическая секция короткая. При высокой конкуренции или сложной многообъектной инвариантности предпочтительнее блокировка: бесконечные или длительные CAS-циклы могут оказаться дороже и сложнее для сопровождения.
Вопрос 1. Достаточно ли после неудачи CAS повторить только саму операцию сравнения и записи?
Ответ. Нет. Нужно заново прочитать текущее состояние и пересчитать новое значение. Предыдущий результат был вычислен на основе устаревшего состояния; повторная запись того же результата может потерять изменения другого потока или нарушить инвариант.
Вопрос 2. Может ли CAS успешно завершиться, хотя состояние объекта логически уже изменилось?
Ответ. Да, это возможно при проблеме ABA. CAS проверяет обычно идентичность значения, например ссылки, а не всю историю изменений: состояние могло измениться с A на B, а затем вернуться к A. Если промежуточное изменение важно, используют версию или метку вместе со значением, например AtomicStampedReference.
Вопрос 3. Почему CAS не является полной заменой блокировкам?
Ответ. CAS удобно применять к одной атомарной переменной или к неизменяемому составному состоянию, представленному одной ссылкой. Но несколько отдельных изменений не становятся одной атомарной операцией автоматически, а при высокой конкуренции повторы создают нагрузку и могут привести к голоданию. Блокировка часто лучше, когда критическая секция длинная, затрагивает много объектов или требует гарантированного продвижения ожидающих потоков.