В каком сценарии многопоточного программирования на Java необходимо использование атомарных переменных (Atomic)? Приведите пример.

Ответ

Атомарные переменные (java.util.concurrent.atomic) необходимы для выполнения простых атомарных операций (чтение-запись-модификация) над примитивами или ссылками в многопоточной среде без использования блокировок (synchronized). Они основаны на аппаратных инструкциях сравнения с обменом (CAS — Compare-And-Swap).

Типичный сценарий: потокобезопасный счётчик.

Проблема с volatile и synchronized:

// Только volatile НЕ решает проблему!
private volatile int counter = 0;
public void unsafeIncrement() {
    counter++; // Операция НЕ атомарна (чтение, инкремент, запись)
}

// Решение с synchronized работает, но может быть избыточным
private int counter = 0;
public synchronized void safeButHeavyIncrement() {
    counter++;
}

Решение с AtomicInteger:

import java.util.concurrent.atomic.AtomicInteger;

public class AtomicCounter {
    // Атомарная переменная
    private final AtomicInteger counter = new AtomicInteger(0);

    public void increment() {
        // Атомарная операция инкремента
        counter.incrementAndGet();
    }

    public int getValue() {
        // Атомарное чтение
        return counter.get();
    }

    // Пример более сложной атомарной операции
    public void updateIfLower(int newValue) {
        int oldValue;
        do {
            oldValue = counter.get();
            // CAS-цикл: обновит значение только если оно всё ещё равно oldValue
        } while (oldValue < newValue && !counter.compareAndSet(oldValue, newValue));
    }
}

Ключевые классы и их применение:

  • AtomicInteger, AtomicLong — счётчики, идентификаторы.
  • AtomicBoolean — флаги инициализации, флаги состояния.
  • AtomicReference<V> — атомарное обновление ссылок на объекты (например, для кэша или конфигурации).
  • AtomicIntegerArray, AtomicLongArray — работа с массивами.

Важное ограничение: Атомарные переменные гарантируют атомарность отдельной операции (например, incrementAndGet()). Для составных действий (проверить, а затем изменить), зависящих от нескольких переменных, всё ещё требуется синхронизация или использование AtomicReference с комплексными объектами.