AtomicReference.compareAndSet: примеры (JAVA)
AtomicReference.compareAndSet(V expect, V update): booleanОбщее описание и сигнатура
Метод AtomicReference.compareAndSet() из пакета java.util.concurrent.atomic выполняет атомарную операцию сравнения и замены значения ссылки. Он используется в многопоточной среде для неблокирующих обновлений разделяемой ссылки без явной синхронизации.
Сигнатура:
public final boolean compareAndSet(V expect, V update)
Параметры:
expect- ожидаемая ссылка (тип V). Сравнение происходит по ссылке (по идентичности), то есть по сравнению текущего значения иexpectкак ссылок.update- новое значение (тип V), которое будет записано при совпадении.
Возвращаемое значение:
true- если текущее значение ссылочной переменной совпало сexpectи замена успешна.false- если текущее значение не совпало сexpect, операция не выполнила запись.
Память и видимость: успешный вызов имеет семантику записи volatile (изменение видимо другим потокам). Неуспешный вызов не выполняет запись и не обеспечивает дополнительных эффектов записи.
Поведение сравнения: сравнение происходит по ссылочной идентичности (эквивалентно проверке current == expect). При использовании объектных типов следует учитывать автопаковку и кэширование (например, Integer.valueOf), это влияет на сравнение по ссылке.
Когда используется: в сценариях без блокировок для реализации неблокирующих алгоритмов (спин-циклы обновления, неблокирующие коллекции, ленивые инициализации и т.д.). Часто применяется вместе с циклами, которые повторяют попытки до успеха.
Короткие примеры применения
Пример 1: простая успешная и неуспешная замена
import java.util.concurrent.atomic.AtomicReference;
public class Demo1 {
public static void main(String[] args) {
AtomicReference ref = new AtomicReference<>("A");
boolean r1 = ref.compareAndSet("A", "B");
boolean r2 = ref.compareAndSet("A", "C");
System.out.println(r1 + ", value=" + ref.get());
System.out.println(r2 + ", value=" + ref.get());
}
}
true, value=B false, value=B
Пример 2: ловушка с автопаковкой Integer (сравнение по ссылке)
import java.util.concurrent.atomic.AtomicReference;
public class Demo2 {
public static void main(String[] args) {
AtomicReference ref = new AtomicReference<>(Integer.valueOf(100));
Integer expect = Integer.valueOf(100); // кэш -128..127, может быть та же ссылка
boolean r = ref.compareAndSet(expect, Integer.valueOf(200));
System.out.println(r + ", value=" + ref.get());
AtomicReference ref2 = new AtomicReference<>(new Integer(100));
boolean r2 = ref2.compareAndSet(new Integer(100), 200); // разные ссылки
System.out.println(r2 + ", value=" + ref2.get());
}
}
true, value=200 false, value=100
Пример 3: простая петля обновления (retry loop)
import java.util.concurrent.atomic.AtomicReference;
public class Demo3 {
public static void main(String[] args) {
AtomicReference ref = new AtomicReference<>("v1");
while (true) {
String cur = ref.get();
String next = cur + "-updated";
if (ref.compareAndSet(cur, next)) {
System.out.println("Updated to " + ref.get());
break;
}
}
}
}
Updated to v1-updated
Похожие Java-методы и выбор между ними
- weakCompareAndSet - похожая по имени операция, допускает ложные неудачи (спонтанные), но может быть быстрее на некоторых архитектурах; подходит для внутренней оптимизации в петлях, допускающих повторные попытки.
- getAndSet - атомарно заменяет значение и возвращает предыдущее; удобен, когда нужно получить старое значение вне цикла.
- AtomicInteger.compareAndSet и другие атомарные числовые типы - аналогичные методы для примитивных типов, работают по значению, не по ссылке.
- AtomicStampedReference / AtomicMarkableReference - решают проблему ABA, позволяют хранить дополнительный маркер или отметку версии вместе со ссылкой.
- VarHandle.compareAndSet - более новый, гибкий механизм (с явным указанием памяти режима) для работы с полями; полезен при низкоуровневой оптимизации.
Когда что предпочтительнее: для простых ссылочных обновлений достаточно AtomicReference.compareAndSet. Если возможна проблема ABA - рассмотреть AtomicStampedReference. Если требуется более низкоуровневый контроль над семантикой памяти, подходят VarHandle-версии.
Альтернативы в других языках и отличия
Краткие сведения и примеры.
- C#:
Interlocked.CompareExchange<T>(ref location, value, comparand). Сравнение по ссылке для ссылочных типов и по значению для примитивов. Возвращает прежнее значение.using System; using System.Threading; class CSharpDemo { static object location = "A"; static void Main() { var prev = Interlocked.CompareExchange(ref location, "B", "A"); Console.WriteLine(prev + ", current=" + location); } }A, current=B
- C / C++: в C11/C++11 есть атомики:
atomic_compare_exchange_strongвозвращает флаг успеха и обновляет ожидаемое значение при неудаче.#include <atomic> #include <iostream> int main(){ std::atomica{1}; int expected = 1; bool ok = a.compare_exchange_strong(expected, 2); std::cout << ok << ", value=" << a << std::endl; } 1, value=2
- Go: пакет
sync/atomicпредоставляетCompareAndSwapInt32/Int64/Pointer. Для произвольных объектов применяетсяatomic.Valueи низкоуровневые операции с unsafe.Pointer.package main import ( "fmt" "sync/atomic" "unsafe" ) func main(){ var p unsafe.Pointer s := "A" atomic.StorePointer(&p, unsafe.Pointer(&s)) old := unsafe.Pointer(&s) t := "B" swapped := atomic.CompareAndSwapPointer(&p, old, unsafe.Pointer(&t)) fmt.Println(swapped) }true
- JavaScript: Web Workers + SharedArrayBuffer + Atomics не дают CAS для объектов, но есть
Atomics.compareExchangeдля типизированных массивов в SharedArrayBuffer.// работает с Int32Array const sab = new SharedArrayBuffer(4); const a = new Int32Array(sab); console.log(Atomics.compareExchange(a, 0, 0, 1));0
- Python: в стандартной библиотеке нет прямого CAS для произвольных ссылок. Примеры через C-расширения, модуль
multiprocessing.Valueс локами или сторонние библиотеки; также можно использоватьatomicwritesи lock-based паттерны.# нет нативного CAS для объектов в CPython; пример с lock from threading import Lock class AtomicRef: def __init__(self, v): self._v = v self._l = Lock() def compare_and_set(self, expect, update): with self._l: if self._v is expect: self._v = update return True return False(работает с блокировкой, но не является неблокирующей)
- PHP: нет встроенного CAS для объектов; можно использовать расширения, POSIX-совместимые примитивы или пользовательские примитивные конструкции с разделяемой памятью и семафорами.
- Lua: стандартно нет; для конкурентных реализаций используются C-биндинги или внешние примитивы.
- Kotlin: на JVM можно использовать те же
AtomicReferenceиз Java или Kotlin-обёртки изkotlin.concurrentиkotlinx.atomicfu, последний предоставляет DSL и компиляторные оптимизации. - SQL: базы данных предлагают атомарные операции через транзакции,
UPDATE ... WHEREс проверкой старого значения или конструкции типаSELECT ... FOR UPDATE, что отличается от CAS по механике (блокировки/транзакции вместо CPU-операции CAS).
Типичные ошибки и примеры
- Ожидание сравнения по equals вместо ссылки. Частая ошибка - ожидание, что
compareAndSetсравнивает поequals(). На деле сравнение по ссылке, поэтому два равных объектов могут не совпадать.AtomicReferencer = new AtomicReference<>(new String("X")); boolean ok = r.compareAndSet(new String("X"), "Y"); System.out.println(ok + ", value=" + r.get()); false, value=X
- Использование автопаковки Integer как ключа без учёта кэширования может давать непредсказуемые результаты: одинаковые значения в диапазоне кэша могут быть одним объектом, а вне диапазона - разными ссылками (см. секцию примеров).
- Ожидание отсутствия ABA-проблемы. CAS не защищает от ситуации, когда значение поменялось с A на B и обратно в A; внешне CAS видит A и позволяет операцию, но логика может быть нарушена. Решение: использовать
AtomicStampedReferenceили версионирование. - Игнорирование возможных спонтанных неудач при
weakCompareAndSet. - Попытки использовать CAS для сложных операций без цикла перезапуска: многие обновления требуют повторения попытки при конфликте.
Изменения и расширения в последних версиях Java
Начиная с Java 9 и далее в стандартной библиотеке появились дополнительные методы и варианты по семантике памяти:
- Добавлены методы наподобие
compareAndExchange,compareAndExchangeAcquireиcompareAndExchangeReleaseвAtomicReferenceдля возвращения предыдущего значения при попытке замены и для управления режимом памяти. - Появились более тонкие варианты слабых операций (
weakCompareAndSetPlain,weakCompareAndSetAcquireи т.д.) в ряде классов и VarHandle API для тонкой настройки семантики видимости. - VarHandle предоставляет аналогичные CAS-операции на полях и массивах с указанием режимов памяти, что даёт большую гибкость для оптимизаций.
Явных изменений в поведении compareAndSet не было; добавлены расширения для новых режимов и API.
Расширенные и редкие сценарии использования
Пример 1: неблокирующий стек (Treiber stack) с использованием AtomicReference<Node>.
import java.util.concurrent.atomic.AtomicReference;
class Node { final int value; final Node next; Node(int v, Node n){ value=v; next=n; }}
public class TreiberStack {
private final AtomicReference top = new AtomicReference<>(null);
public void push(int v){
Node newNode;
do {
Node cur = top.get();
newNode = new Node(v, cur);
} while (!top.compareAndSet(newNode.next, newNode));
}
public Integer pop(){
Node cur;
do {
cur = top.get();
if (cur == null) return null;
} while (!top.compareAndSet(cur, cur.next));
return cur.value;
}
public static void main(String[] args){
TreiberStack s = new TreiberStack();
s.push(1); s.push(2);
System.out.println(s.pop());
System.out.println(s.pop());
}
}
2 1
Комментарий: стек неблокирующий, но подвержен ABA; при необходимости добавить штамп версии.
Пример 2: защита от ABA с AtomicStampedReference
import java.util.concurrent.atomic.AtomicStampedReference;
public class ABAExample {
public static void main(String[] args){
AtomicStampedReference r = new AtomicStampedReference<>("A", 0);
int[] stamp = new int[1];
String v = r.get(stamp);
int s = stamp[0];
// другой поток поменял A -> B -> A, увеличив штамп
r.set("A", s+1);
boolean ok = r.compareAndSet(v, "C", s, s+2);
System.out.println(ok + ", value=" + r.getReference() + ", stamp=" + r.getStamp());
}
}
false, value=A, stamp=1
Пример 3: ленивый инициализатор без блоков
import java.util.concurrent.atomic.AtomicReference;
public class LazyInit {
private final AtomicReference ref = new AtomicReference<>();
private final java.util.function.Supplier supplier;
public LazyInit(java.util.function.Supplier s){ supplier = s; }
public T get(){
T v = ref.get();
if (v != null) return v;
T newVal = supplier.get();
if (ref.compareAndSet(null, newVal)) return newVal;
else return ref.get();
}
}
// Результат: объект создаётся один раз конкурентно без synchronized
(создаёт объект один раз, возвращает ссылку на инициализированный объект)
Пример 4: использование weakCompareAndSet в tight-loop
AtomicReference r = new AtomicReference<>("x");
boolean success;
do {
String cur = r.get();
String next = cur + "!";
success = r.weakCompareAndSet(cur, next);
} while (!success);
// weakCompareAndSet может возвращать false спонтанно, поэтому цикл необходим
(в итоге значение обновлено, но могут быть дополнительные повторы)
Пример 5: использование compareAndExchange (Java 9+) для получения старого значения
// Требует Java 9+
import java.util.concurrent.atomic.AtomicReference;
public class ExchangeDemo {
public static void main(String[] args){
AtomicReference r = new AtomicReference<>("old");
String prev = r.compareAndExchange("old", "new");
System.out.println(prev + ", now=" + r.get());
}
}
old, now=new
джава AtomicReference.compareAndSet function comments
- джава AtomicReference.compareAndSet - аргументы и возвращаемое значение
- Функция java AtomicReference.compareAndSet - описание
- AtomicReference.compareAndSet - примеры
- AtomicReference.compareAndSet - похожие методы на java
- AtomicReference.compareAndSet на javascript, c#, python, php
- AtomicReference.compareAndSet изменения java
- Примеры AtomicReference.compareAndSet на джава