← Назад к вопросам

Как устроена AtomicReference?

2.0 Middle🔥 161 комментариев
#Основы Java

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Как устроена AtomicReference

AtomicReference — это потокобезопасный контейнер для хранения ссылки на объект. Это часть пакета java.util.concurrent.atomic и позволяет безопасно обновлять объект в многопоточной среде без явной синхронизации.

Основная идея

AtomicReference использует CAS (Compare-And-Swap) операции на уровне процессора, чтобы атомарно обновить значение. Это дешевле, чем использовать synchronized блоки.

// Базовый пример
AtomicReference<User> userRef = new AtomicReference<>(null);

// Потокобезопасное присвоение
User user = new User("John");
userRef.set(user);

// Потокобезопасное чтение
User current = userRef.get();

// Атомарное сравнение и обновление
User oldUser = new User("John");
User newUser = new User("Jane");
boolean updated = userRef.compareAndSet(oldUser, newUser);

Структура и реализация

Упрощённая реализация:

public class AtomicReference<V> implements java.io.Serializable {
    private static final long serialVersionUID = -1848883965231344442L;
    
    // Используются специальные методы для получения offset-а
    private static final sun.misc.Unsafe UNSAFE = 
        sun.misc.Unsafe.getUnsafe();
    
    // Это смещение в памяти поля value
    private static final long VALUE;
    
    static {
        try {
            VALUE = UNSAFE.objectFieldOffset
                (AtomicReference.class.getDeclaredField("value"));
        } catch (Exception ex) { throw new Error(ex); }
    }
    
    // Это основное хранилище значения
    private volatile V value;
    
    public AtomicReference(V initialValue) {
        value = initialValue;
    }
    
    public AtomicReference() {
    }
    
    // Get и Set
    public final V get() {
        return value;  // volatile read
    }
    
    public final void set(V newValue) {
        value = newValue;  // volatile write
    }
    
    // CAS операция
    public final boolean compareAndSet(V expect, V update) {
        return UNSAFE.compareAndSwapObject(this, VALUE, expect, update);
    }
    
    // Lazy Set — оптимизированный вариант
    public final void lazySet(V newValue) {
        UNSAFE.putOrderedObject(this, VALUE, newValue);
    }
    
    // Get and Set
    public final V getAndSet(V newValue) {
        return (V)UNSAFE.getAndSetObject(this, VALUE, newValue);
    }
}

Ключевые компоненты

1. volatile ключевое слово:

private volatile V value;

Это гарантирует:

  • Видимость между потоками (по Java Memory Model)
  • Нет кеширования значения в регистрах процессора
  • Каждое чтение и запись идёт в основную память

2. Unsafe класс:

// Это низкоуровневый API для работы с памятью
UNSAFE.compareAndSwapObject(this, VALUE, expect, update);

Этот метод использует CAS инструкцию процессора (CMPXCHG на x86):

  • Атомарно сравнивает текущее значение с expect
  • Если равны — обновляет на update
  • Возвращает true если успешно, false если не совпал

3. VALUE — смещение поля в памяти:

private static final long VALUE;

static {
    VALUE = UNSAFE.objectFieldOffset(
        AtomicReference.class.getDeclaredField("value")
    );
}

ЭтоVалюе показывает, где находится поле value в памяти объекта.

Как работает CAS операция

// Без CAS (могут быть race conditions):
V oldValue = reference.get();     // 1. Прочитали
V newValue = oldValue.update();   // 2. Обновили
reference.set(newValue);          // 3. Записали
// Между шагом 1 и 3 другой поток может изменить reference!

// С CAS (атомарно):
boolean success = reference.compareAndSet(oldValue, newValue);
if (!success) {
    // Другой поток изменил значение, повторить
}

Практические примеры

Пример 1: Безопасное обновление кеша

public class ConfigCache {
    private AtomicReference<Configuration> config = 
        new AtomicReference<>(null);
    
    public void updateConfig(Configuration newConfig) {
        config.set(newConfig);
    }
    
    public Configuration getConfig() {
        return config.get();
    }
}

Пример 2: Compare-and-Set логика

public class SharedCounter {
    private AtomicReference<Integer> value = 
        new AtomicReference<>(0);
    
    // Безопасное инкрементирование
    public void increment() {
        Integer current;
        Integer next;
        do {
            current = value.get();
            next = current + 1;
        } while (!value.compareAndSet(current, next));
        // Повторяем, пока не успешно обновим
    }
}

Пример 3: Ленивая инициализация

public class LazyInitializer<T> {
    private AtomicReference<T> instance = 
        new AtomicReference<>(null);
    
    private final Supplier<T> factory;
    
    public LazyInitializer(Supplier<T> factory) {
        this.factory = factory;
    }
    
    public T getInstance() {
        T value = instance.get();
        if (value != null) {
            return value;  // Быстрый путь — без блокировки
        }
        
        // Только если нужно инициализировать
        T newValue = factory.get();
        if (instance.compareAndSet(null, newValue)) {
            return newValue;
        } else {
            return instance.get();  // Кто-то другой инициализировал
        }
    }
}

Методы AtomicReference

AtomicReference<User> ref = new AtomicReference<>(new User("John"));

// get() — прочитать значение
User user = ref.get();

// set() — установить значение
ref.set(new User("Jane"));

// getAndSet() — атомарно получить и установить
User oldUser = ref.getAndSet(new User("Bob"));

// compareAndSet() — условное обновление
boolean updated = ref.compareAndSet(
    new User("Bob"),
    new User("Charlie")
);

// lazySet() — оптимизированный set (может быть отложен)
ref.lazySet(new User("Dave"));

// weakCompareAndSet() — слабая версия CAS (может быть быстрее)
boolean weakUpdated = ref.weakCompareAndSet(
    new User("Dave"),
    new User("Eve")
);

Performance характеристики

Когда использовать AtomicReference:

  • ✅ Низкая конкуренция за значение
  • ✅ Частые читаю, редкие обновления
  • ✅ Нужна максимальная производительность

Когда НЕ использовать:

  • ❌ Высокая конкуренция (много потоков пытаются обновить)
  • ❌ Нужна сложная логика синхронизации
  • ❌ Нужен lock

Сравнение с синхронизацией

// С synchronized (выше overhead)
synchronized(this) {
    value = newValue;
}

// С AtomicReference (быстрее, нет блокировок)
atomicRef.set(newValue);

// CAS в цикле (когда нужно условное обновление)
do {
    current = atomicRef.get();
    next = compute(current);
} while (!atomicRef.compareAndSet(current, next));

Итог

AtomicReference — это основной инструмент для lock-free программирования в Java. Она использует низкоуровневые CAS операции процессора, чтобы достичь максимальной производительности без блокировок. Это особенно важно в высоконагруженных системах, где конкуренция за ресурсы может серьёзно повлиять на производительность.