AtomicReference.set: примеры (JAVA)
AtomicReference.set(V newValue): voidОписание метода AtomicReference.set
Метод AtomicReference.set(V newValue) из пакета java.util.concurrent.atomic выполняет присваивание нового значения ссылочному контейнеру AtomicReference. Подпись метода:
public void set(V newValue)
Ключевые свойства и семантика:
- Метод возвращает void. Предыдущего значения не возвращается.
- Операция выполняется как запись с семантикой volatile: значение становится видимым для других потоков не позже, чем при обычной volatile-записи.
- Аргумент newValue может быть
null. AtomicReference допускает хранениеnull. - Метод атомарно записывает ссылку, в том смысле, что обновление ссылки не будет частично видимо. Но он не выполняет проверку предыдущего значения и не обеспечивает сравнения по условию. Для условных обновлений применяются методы compareAndSet и weakCompareAndSet.
- Для других моделей видимости доступны lazySet (заказанная/отложенная запись) и API VarHandle (Java 9+), дающие более тонкий контроль над порядком записей.
Параметров, флагов или перегрузок у set нет: только один аргумент нового значения. Возвращаемое значение отсутствует. Важное отличие от getAndSet состоит в том, что getAndSet возвращает предыдущее значение и выполняет атомарную замену с возвратом старого значения, а set просто записывает новое.
Типовые сценарии применения: хранение изменяемой ссылки, передаваемой между потоками, простые обновления состояния, сброс ссылок при очистке ресурсов, инициализация или замена значения без необходимости узнать предыдущее.
Короткие примеры использования AtomicReference.set()
Пример 1. Простая запись и чтение.
import java.util.concurrent.atomic.AtomicReference;
public class Example1 {
public static void main(String[] args) {
AtomicReference ref = new AtomicReference<>("initial");
System.out.println(ref.get());
ref.set("updated");
System.out.println(ref.get());
}
}
initial updated
Пример 2. Установка null как допустимое значение.
import java.util.concurrent.atomic.AtomicReference;
public class ExampleNull {
public static void main(String[] args) {
AtomicReference ref = new AtomicReference<>("value");
ref.set(null);
System.out.println(ref.get() == null ? "null" : ref.get());
}
}
null
Пример 3. Видимость между потоками: один поток записывает, другой читает.
import java.util.concurrent.atomic.AtomicReference;
public class VisibilityExample {
public static void main(String[] args) throws InterruptedException {
AtomicReference ref = new AtomicReference<>("start");
Thread writer = new Thread(() -> {
try { Thread.sleep(200); } catch (InterruptedException ignored) {}
ref.set("from-writer");
});
Thread reader = new Thread(() -> {
while (!"from-writer".equals(ref.get())) {
// ждать обновления
}
System.out.println("reader observed: " + ref.get());
});
writer.start();
reader.start();
writer.join();
reader.join();
}
}
reader observed: from-writer
Похожие API в Java и их отличия
- getAndSet(V newValue) - атомарно заменяет значение и возвращает предыдущее. Применяется, если важно старое значение.
- compareAndSet(V expect, V update) - условное обновление: меняет значение только если текущее равно ожидаемому. Подходит для неблокирующих алгоритмов.
- lazySet(V newValue) - более слабая по гарантиям запись: запись будет видима позже, но обычно быстрее на некоторых архитектурах. Используется при необходимости избежать немедленной синхронизации видимости.
- weakCompareAndSet(V expect, V update) - может возвращать ложь при спорадическом провале; полезно в петлях с повторениями для повышения производительности.
- AtomicReferenceFieldUpdater - обновление ссылок в полях объектов без выделения отдельного AtomicReference для каждого поля. Предпочтительно при экономии памяти.
- VarHandle (Java 9+) - более гибкий доступ к памяти с контролем порядков операций и возможностью заменять AtomicReference в некоторых сценариях.
- volatile поле и синхронизированные блоки - простые альтернативы для управления видимостью и атомарностью составных операций, когда требуется больше контроля над логикой.
Выбор зависит от задачи: если нужно просто записать значение - set; нужен старый результат - getAndSet; требуется условная замена без блокировок - compareAndSet; нужна экономия памяти - AtomicReferenceFieldUpdater.
Аналоги в других языках и отличия
Краткие соответствия и примеры.
- C#: Interlocked.Exchange для ссылочных типов. Семантика похожа на getAndSet (возвращает предыдущее значение). Для простой записи можно использовать Volatile.Write.
// C#
using System;
using System.Threading;
class CSharpExample {
static object refObj = "start";
static void Main() {
var old = Interlocked.Exchange(ref refObj, "updated");
Console.WriteLine(old);
Console.WriteLine(refObj);
}
}
start updated
- Go: atomic.Value.Store полезен для записи значения с атомарной видимостью. atomic.Value хранит значения интерфейсного типа и обеспечивает безопасный доступ между горутинами.
// Go
package main
import (
"fmt"
"sync/atomic"
)
func main() {
var v atomic.Value
v.Store("initial")
fmt.Println(v.Load())
v.Store("next")
fmt.Println(v.Load())
}
initial next
- Python: отсутствует встроенный атомарный контейнер для произвольных объектов в стандартной библиотеке. Обычно применяется
threading.Lockили внешние библиотеки. multiprocessing.Value подходит для примитивов между процессами.
# Python
import threading
ref = None
lock = threading.Lock()
with lock:
ref = "value"
print(ref)
value
- JavaScript: в браузере и Node.js обычный код однопоточный, поэтому простая запись безопасна. Для разделяемой памятью многопоточности (SharedArrayBuffer) доступны Atomics.store и Atomics.load, но только для типизированных числовых массивов.
// JavaScript (Node.js или браузер)
const sab = new SharedArrayBuffer(4);
const arr = new Int32Array(sab);
Atomics.store(arr, 0, 42);
console.log(Atomics.load(arr, 0));
42
- Kotlin: использует Java AtomicReference без изменений; доступны те же методы.
- Rust: AtomicPtr или атомарные примитивы. Для ссылочных типов чаще применяются Arc
> или атомарные указатели. - Lua: в стандартной реализации нет атомарных примитивов; синхронизация достигается через внешние механизмы или C-биндинги.
Отличия от Java: в большинстве языков атомарные операции для ссылок либо представлены другими API (Interlocked в C#), либо отсутствуют для произвольных объектов и эмулируются блокировками. Некоторые языки (Go, C#) имеют встроенные атомарные примитивы, в которых семантика близка к AtomicReference.
Типичные ошибки при использовании AtomicReference.set()
- Ожидание возвращаемого значения. set возвращает void; если требуется предыдущее значение, применяется getAndSet.
- Неправильная оценка атомарности составных операций. Две последовательные set не атомарны как единое действие; для условной смены требуется compareAndSet.
- Использование lazySet вместо set при необходимости немедленной видимости. lazySet может отложить видимость на некоторых платформах.
- Использование set в алгоритмах, требующих проверки и замены по условию, вместо compareAndSet, что может привести к гонкам.
Пример ошибки: попытка реализовать счетчик ссылок, полагаясь только на set.
import java.util.concurrent.atomic.AtomicReference;
public class WrongExample {
static class Counter { int value; }
public static void main(String[] args) throws InterruptedException {
AtomicReference ref = new AtomicReference<>(new Counter());
Thread t1 = new Thread(() -> {
Counter c = ref.get();
c.value++;
ref.set(c); // гонка: другой поток мог уже изменить объект
});
Thread t2 = new Thread(() -> {
Counter c = ref.get();
c.value++;
ref.set(c);
});
t1.start(); t2.start();
t1.join(); t2.join();
System.out.println(ref.get().value);
}
}
(результат может быть 1 или 2 в зависимости от гонки)
В этом примере нужно или делать копию объекта перед изменением и использовать compareAndSet, или применять примитивы для примитивных типов (AtomicInteger) или синхронизацию.
Изменения и эволюция API
- AtomicReference присутствует с Java 5 как часть java.util.concurrent.atomic.
- В Java 8 добавлены методы для функциональных обновлений: getAndUpdate, updateAndGet, getAndAccumulate, accumulateAndGet, что расширило возможности атомарных обновлений с функциями.
- В Java 9 введен API VarHandle, предоставляющий более гибкие операции над памятью и заменяющий часть задач, решаемых AtomicReference, особенно когда требуется более детальный контроль порядка операций.
- Поведение метода set как volatile-записи не изменялось, существенных изменений в сигнатуре не происходило.
Расширенные и редкие сценарии применения
Пример 1. Неблокирующий стек (Treiber stack) на AtomicReference с использованием compareAndSet.
import java.util.concurrent.atomic.AtomicReference;
public class LockFreeStack {
static class Node { E value; Node next; Node(E v, Node n) { value = v; next = n; } }
private final AtomicReference> head = new AtomicReference<>();
public void push(E value) {
Node newHead;
Node oldHead;
do {
oldHead = head.get();
newHead = new Node<>(value, oldHead);
} while (!head.compareAndSet(oldHead, newHead));
}
public E pop() {
Node oldHead;
Node newHead;
do {
oldHead = head.get();
if (oldHead == null) return null;
newHead = oldHead.next;
} while (!head.compareAndSet(oldHead, newHead));
return oldHead.value;
}
public static void main(String[] args) {
LockFreeStack stack = new LockFreeStack<>();
stack.push(1);
stack.push(2);
System.out.println(stack.pop());
System.out.println(stack.pop());
}
}
2 1
Комментарий: в этом примере set не используется, но показана типичная альтернатива - compareAndSet. Для простых замен без условия set применяется, но в неблокирующих структурах чаще нужны CAS-операции.
Пример 2. Ленивый одноразовый инициализатор с использованием compareAndSet и set.
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Supplier;
public class LazyInit {
private final AtomicReference ref = new AtomicReference<>();
public T getOrCreate(Supplier supplier) {
T val = ref.get();
if (val == null) {
T newVal = supplier.get();
if (ref.compareAndSet(null, newVal)) {
return newVal;
} else {
return ref.get();
}
}
return val;
}
public static void main(String[] args) {
LazyInit li = new LazyInit<>();
System.out.println(li.getOrCreate(() -> "created"));
}
}
created
Комментарий: здесь set не используется напрямую, но показана общая схема: set пригодится при простых заменах, а CAS для инициализации один раз при конкуренции.
Пример 3. Комбинация AtomicReference.set и getAndUpdate для функционального обновления.
import java.util.concurrent.atomic.AtomicReference;
public class UpdateExample {
public static void main(String[] args) {
AtomicReference ref = new AtomicReference<>(0);
Integer prev = ref.getAndUpdate(x -> x + 10); // возвращает старое значение
System.out.println("prev=" + prev + ", now=" + ref.get());
ref.set(100); // простая запись
System.out.println("after set: " + ref.get());
}
}
prev=0, now=10 after set: 100
Пример 4. Использование AtomicReferenceFieldUpdater для экономии памяти: обновление поля в массивах объектов вместо создания AtomicReference для каждого объекта.
import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
class Item {
volatile String value;
static final AtomicReferenceFieldUpdater- UPDATER =
AtomicReferenceFieldUpdater.newUpdater(Item.class, String.class, "value");
}
public class FieldUpdaterExample {
public static void main(String[] args) {
Item it = new Item();
it.value = "a";
UPDATER.compareAndSet(it, "a", "b");
System.out.println(it.value);
}
}
b
Комментарий: AtomicReferenceFieldUpdater позволяет избежать выделения отдельного AtomicReference для каждого поля и подходит при больших структурах данных.