AtomicReference.set: примеры (JAVA)

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.

Пример java
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.

Пример java
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 для функционального обновления.

Пример java
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 для каждого объекта.

Пример java
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 для каждого поля и подходит при больших структурах данных.

джава AtomicReference.set function comments

En
AtomicReference.set Установить новое значение