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

Метод AtomicReference.get() - описание
Раздел: Многопоточность, Атомарные классы
AtomicReference.get(): V

Общее описание метода

Метод AtomicReference.get() возвращает текущее значение, хранящееся в экземпляре java.util.concurrent.atomic.AtomicReference<V>. Это атомарная операция чтения, гарантирующая, что возвращаемое значение соответствует некоторому состоянию ссылки в памяти без дополнительных блокировок. Метод полезен при реализации неблокирующих структур данных и при передаче ссылок между потоками.

Сигнатура:

public V get()

Аргументы: метод не принимает аргументов.

Возвращаемое значение: возвращается объект типа V - текущее значение ссылки. Если ссылка не инициализирована, возвращается null. Метод не бросает исключений в нормальных условиях.

Поведение и наблюдаемость: вызов get() обеспечивает атомарное чтение одной ссылки. В многопоточном окружении другие операции, такие как set, compareAndSet или getAndSet, могут одновременно изменять значение; get() вернёт либо значение до изменения, либо после, но не промежуточное. Для более тонкого управления памятью в Java 9+ доступны родственные методы с семантикой памяти (getPlain, getOpaque, getAcquire), которые дают разные гарантии видимости и упорядочения.

Простые варианты использования

Ниже приведено несколько коротких примеров с выводом результата.

1) Базовый пример чтения значения:

import java.util.concurrent.atomic.AtomicReference;

public class Example1 {
    public static void main(String[] args) {
        AtomicReference ref = new AtomicReference<>("hello");
        System.out.println(ref.get());
    }
}
hello

2) Чтение null если значение не задано:

AtomicReference ref = new AtomicReference<>();
System.out.println(ref.get());
null

3) Получение после конкурентной смены значения другим потоком:

AtomicReference ref = new AtomicReference<>(1);
Thread t = new Thread(() -> ref.set(2));
t.start();
try { t.join(); } catch (InterruptedException ignored) {}
System.out.println(ref.get());
2

Похожие методы в Java

Список альтернатив и их краткие особенности:

  • getAndSet(V newValue) - атомарная замена возвращает старое значение; полезно когда требуется атомарная «замена и чтение».
  • set(V newValue) - простая запись без возврата предыдущего значения.
  • compareAndSet(V expect, V update) - атомарное условное обновление; рекомендуется при реализации неблокирующих алгоритмов, где нужна проверка и обновление в одной атомарной операции.
  • getAndUpdate / updateAndGet / accumulateAndGet / getAndAccumulate (Java 8) - атомарные обновления с функцией; удобны при трансформации значения на основе предыдущего.
  • VarHandle / AtomicReferenceFieldUpdater - более продвинутые механизмы доступа к полям и управление семантикой памяти; пригодны при необходимости тонкой оптимизации или работы с нестатическими полями.

Выбор зависит от целей: если требуется только чтение - get() достаточно; если нужна атомарная замена - getAndSet; если необходима условная смена - compareAndSet; для пользовательских преобразований - методы с функциями.

Аналоги в других языках

Короткие сравнения с примерами.

C# - классическая альтернатива: Interlocked.CompareExchange и Volatile.Read.

using System;
using System.Threading;

class CSharpExample {
    static object refObj = "hello";
    static void Main() {
        Console.WriteLine(Volatile.Read(ref refObj));
    }
}
hello

Go - пакет sync/atomic и тип atomic.Value для хранения произвольных значений.

package main

import (
    "fmt"
    "sync/atomic"
)

func main() {
    var v atomic.Value
    v.Store("hello")
    fmt.Println(v.Load())
}
hello

Python - явной атомарной обертки для ссылок нет в стандартной библиотеке; чтение ссылки под GIL часто выглядит атомарным, но для гарантированной межпоточной синхронизации применяется threading.Lock.

from threading import Lock

ref = None
lock = Lock()
with lock:
    ref = 'hello'
with lock:
    print(ref)
hello

JavaScript - в браузере и Node.js нет прямого аналога для объектов; для совместного доступа к общим буферам используются SharedArrayBuffer и Atomics, но они работают с числовыми массивами.

// Пример с числовым буфером
const sab = new SharedArrayBuffer(4);
const ia = new Int32Array(sab);
Atomics.store(ia, 0, 42);
console.log(Atomics.load(ia, 0));
42

Kotlin - использует Java-реализацию: java.util.concurrent.atomic.AtomicReference. Поведение аналогично Java.

PHP, Lua, SQL - стандартных атомарных ссылок нет; требуется внешняя синхронизация, блокировки или специализированные расширения.

Типичные ошибки и подводные камни

  • Ожидание транзакционной атомарности нескольких операций. Прочитать значение через get(), затем изменить через set() - это не атомарная пара; для условного обновления требуется compareAndSet или методы с функциями.
  • Ошибка при предположении, что get() никогда вернёт null. Если AtomicReference инициализировался без значения, результат будет null и возможен NullPointerException при последующей обработке без проверки.
  • Использование небезопасных приведений типов при применении «сырых» типов без generics может привести к ClassCastException или предупреждениям компилятора.
  • Ожидание упорядочения операций по сравнению с VarHandle-семантикой. В Java 9+ есть специализированные методы с разной семантикой памяти; использование get() даёт обычные гарантии видимости, но при тонкой оптимизации может потребоваться getAcquire или VarHandle.

Пример неправильной попытки сделать условную смену только с get() и set():

AtomicReference ref = new AtomicReference<>(0);
// Неправильно в многопоточном окружении
if (ref.get() == 0) {
    ref.set(1); // между get и set другой поток мог уже изменить значение
}
Поведение гонки: значение может оказаться 1, 2 или иным в зависимости от других потоков

Изменения и дополнения в Java

Эволюция класса:

  • Java 8: добавлены методы функционального обновления - getAndUpdate, updateAndGet, getAndAccumulate, accumulateAndGet. Они упрощают атомарные трансформации значения с использованием лямбда-выражений.
  • Java 9: добавлены методы с различными семантиками памяти, такие как getPlain, getOpaque, getAcquire и соответствующие версии для записи (setPlain, setOpaque, setRelease). Эти методы дают более тонкий контроль над порядком видимости операций и позволяют оптимизировать производительность на некоторых архитектурах.
  • Рекомендация: для новых разработок, требующих низкоуровневой семантики памяти, рассмотреть VarHandle. Для большинства задач стандартный get() остаётся корректным и достаточно понятным решением.

Расширенные и редкие сценарии применения

Несколько нестандартных и полезных примеров с объяснениями.

1) Неблокирующая односвязная стэк на AtomicReference (push/pop):

Пример java
import java.util.concurrent.atomic.AtomicReference;

class Node {
    final T value;
    final Node next;
    Node(T v, Node n) { value = v; next = n; }
}

class LockFreeStack {
    private final AtomicReference> head = new AtomicReference<>();

    void push(T value) {
        Node newNode;
        do {
            Node oldHead = head.get();
            newNode = new Node<>(value, oldHead);
        } while (!head.compareAndSet(newNode.next, newNode));
    }

    T pop() {
        Node oldHead;
        do {
            oldHead = head.get();
            if (oldHead == null) return null;
        } while (!head.compareAndSet(oldHead, oldHead.next));
        return oldHead.value;
    }
}

// Демонстрация
public class StackDemo {
    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

Комментарий: в этом примере get() используется для чтения текущей головы стека перед попыткой CAS. Операции чтения и CAS обеспечивают корректность без блокировок.

2) Комбинация get() с updateAndGet для вычисления и записи на основе предыдущего значения:

Пример java
import java.util.concurrent.atomic.AtomicReference;

public class UpdateExample {
    public static void main(String[] args) {
        AtomicReference ref = new AtomicReference<>(10);
        System.out.println("before: " + ref.get());
        Integer result = ref.updateAndGet(x -> x * 2);
        System.out.println("after: " + result);
    }
}
before: 10
after: 20

3) Использование get() для считывания в цепочке проверок с памятью acquire/release (Java 9+):

Пример java
AtomicReference ref = new AtomicReference<>("a");
// При необходимости можно заменить на getAcquire() для специальных случаев
String v = ref.get();
// далее логика, зависящая от v
System.out.println(v);
a

4) Диагностика и отладка: логирование состояния в конкурентной системе. Простое чтение через get() даёт снимок, который полезен для трассировки, но не заменяет синхронизацию для корректного восстановления состояния.

5) Использование с неблокирующими кешами: AtomicReference хранит ссылку на неизменяемую структуру данных; чтение через get() позволяет безопасно получить версию без копирования.

Пример java
// immutable snapshot pattern
AtomicReference treeRef = new AtomicReference<>(new MyImmutableTree());
MyImmutableTree snapshot = treeRef.get();
// безопасное чтение из snapshot без синхронизации
(нет прямого текстового вывода - snapshot используется для чтения)

Пояснение: везде, где требуется только доступ к текущему значению без модификации, get() соответствует простому, быстому и потокобезопасному чтению.

джава AtomicReference.get() function comments

En
AtomicReference.get() Получить текущее значение