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

Как работают атомарные типы данных в Java

2.3 Middle🔥 201 комментариев
#Многопоточность

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

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

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

Как работают атомарные типы данных в Java

Атомарные типы (Atomic classes) — это специальные классы из пакета java.util.concurrent.atomic, которые позволяют выполнять операции над переменными потокобезопасно без использования synchronized.

Проблема без атомарных типов

public class Counter {
    private int count = 0;  // ❌ Race condition!
    
    public void increment() {
        count++;  // Это НЕ атомарная операция!
    }
    
    public int getCount() {
        return count;
    }
}

Операция count++ состоит из трёх шагов:

  1. Read: прочитать текущее значение (count = 5)
  2. Modify: увеличить (count = 6)
  3. Write: записать обратно (count = 6)

Если два потока выполняют это одновременно:

Поток 1: Read(5) → Modify(6) → Write(6)
Поток 2: Read(5) → Modify(6) → Write(6)
Результат: count = 6, но должно быть 7!

Решение: AtomicInteger

import java.util.concurrent.atomic.AtomicInteger;

public class SafeCounter {
    private AtomicInteger count = new AtomicInteger(0);
    
    public void increment() {
        count.incrementAndGet();  // ✅ Атомарная операция
    }
    
    public int getCount() {
        return count.get();
    }
}

Как это работает на уровне CPU

Атомарные операции используют специальные CPU инструкции:

// Java код
atomicCounter.incrementAndGet();

// На уровне CPU (x86-64):
// LOCK XADD — атомарная инструкция
// LOCK приставка гарантирует, что других потоков не будет доступа

Другие потоки просто ждут, пока завершится операция.

Основные AtomicInteger операции

AtomicInteger ai = new AtomicInteger(10);

// Чтение
int value = ai.get();  // = 10

// Запись
ai.set(20);

// Атомарное увеличение и возврат нового значения
int newValue = ai.incrementAndGet();  // = 21, возвращает 21

// Атомарное увеличение и возврат старого значения
int oldValue = ai.getAndIncrement();  // = 21, возвращает 20

// Атомарное уменьшение
ai.decrementAndGet();  // = 20
ai.getAndDecrement();  // возвращает 20, значение становится 19

// Прибавление значения
ai.addAndGet(5);  // = 24, возвращает 24
ai.getAndAdd(5);  // возвращает 24, значение становится 29

// Compare-And-Set (CAS) — основная операция
boolean success = ai.compareAndSet(29, 100);
// Если текущее значение == 29, то установить 100 и вернуть true
// Иначе вернуть false

Compare-And-Set (CAS) — сердце atomics

Это основная операция, на которой построены все остальные:

public class AtomicInteger {
    private volatile int value;
    
    // CAS операция
    public final boolean compareAndSet(int expectedValue, int newValue) {
        // Проверка и запись происходит атомарно
        // Если value == expectedValue, то value = newValue
        // Вернуть успех операции
    }
    
    // incrementAndGet реализована через CAS
    public final int incrementAndGet() {
        for (;;) {  // Бесконечный цикл
            int current = get();
            int next = current + 1;
            if (compareAndSet(current, next)) {
                return next;  // Успешно!
            }
            // Если CAS не удалась (другой поток изменил), повтори
        }
    }
}

Важная роль volatile

У AtomicInteger поле value объявлено как volatile:

public class AtomicInteger {
    private volatile int value;  // ← volatile гарантирует видимость
    
    public final int get() {
        return value;  // Всегда читаем из памяти, не из кэша
    }
}

volatile означает:

  • Visibility: изменения видны всем потокам
  • Ordering: операции выполняются в определённом порядке
  • No atomicity: но сама операция не атомарна (value++ всё ещё небезопасна)

Основные AtomicClasses

import java.util.concurrent.atomic.*;

// Примитивные типы
AtomicInteger ai = new AtomicInteger(0);
AtomicLong al = new AtomicLong(0);
AtomicBoolean ab = new AtomicBoolean(false);

// Ссылки на объекты
AtomicReference<String> ar = new AtomicReference<>("initial");
String value = ar.get();
ar.set("new value");
ar.compareAndSet("new value", "another");

// Массивы атомарных элементов
AtomicIntegerArray aia = new AtomicIntegerArray(10);
aia.set(0, 100);  // Установить элемент [0] = 100
aia.getAndIncrement(0);  // Увеличить элемент [0]

// Для сложных обновлений
AtomicReference<Complex> acr = new AtomicReference<>(new Complex(1, 2));
acr.getAndUpdate(c -> c.add(1));  // Lambda для обновления
acr.updateAndGet(c -> c.multiply(2));

Практический пример: счётчик запросов

public class RequestCounter {
    private AtomicLong totalRequests = new AtomicLong(0);
    private AtomicLong successfulRequests = new AtomicLong(0);
    private AtomicLong failedRequests = new AtomicLong(0);
    
    public void recordRequest(boolean success) {
        totalRequests.incrementAndGet();
        if (success) {
            successfulRequests.incrementAndGet();
        } else {
            failedRequests.incrementAndGet();
        }
    }
    
    public Statistics getStats() {
        return new Statistics(
            totalRequests.get(),
            successfulRequests.get(),
            failedRequests.get()
        );
    }
}

// Использование в многопоточной среде
RequestCounter counter = new RequestCounter();
ExecutorService executor = Executors.newFixedThreadPool(10);

for (int i = 0; i < 1000; i++) {
    executor.submit(() -> counter.recordRequest(Math.random() > 0.1));
}

executor.shutdown();
System.out.println(counter.getStats());  // Точные результаты!

AtomicReference для сложных объектов

public class UserStore {
    private AtomicReference<User> currentUser = new AtomicReference<>();
    
    // Безопасное обновление
    public void updateUser(UnaryOperator<User> updater) {
        currentUser.updateAndGet(updater);
    }
    
    // Применение
    userStore.updateUser(user -> {
        user.setLastLogin(Instant.now());
        user.incrementLoginCount();
        return user;
    });
}

Производительность vs synchronized

// ❌ synchronized — может быть медленнее при низкой конкуренции
public synchronized void increment() {
    count++;
}

// ✅ AtomicInteger — обычно быстрее
private AtomicInteger count = new AtomicInteger(0);
public void increment() {
    count.incrementAndGet();
}

Почему AtomicInteger часто быстрее:

  • synchronized блокирует поток полностью (контекстный switch дорог)
  • AtomicInteger использует CAS — поток может повторить, но не блокируется
  • Кэширование работает лучше

Главные выводы

  1. Atomic = безопасность без synchronized

    • Используй вместо volatile int
    • Используй вместо synchronized для простых счётчиков
  2. Основа — CAS (Compare-And-Swap)

    • Проверка и обновление в одну атомарную операцию
    • Если не удалась — повтор
  3. volatile внутри

    • Гарантирует видимость изменений
    • Но не атомарность самой операции
  4. Когда использовать:

    • Счётчики, флаги в многопоточной среде
    • Простые значения (int, long, boolean)
    • Когда нужна высокая производительность
  5. Когда использовать synchronized вместо:

    • Сложные операции (несколько переменных)
    • Когда нужна гарантия взаимного исключения для блока кода

Атомарные типы — это ключевой инструмент для написания эффективного многопоточного Java кода.

Как работают атомарные типы данных в Java | PrepBro