Как работают атомарные типы данных в Java
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Как работают атомарные типы данных в 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++ состоит из трёх шагов:
- Read: прочитать текущее значение (count = 5)
- Modify: увеличить (count = 6)
- 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 — поток может повторить, но не блокируется
- Кэширование работает лучше
Главные выводы
-
Atomic = безопасность без
synchronized- Используй вместо
volatile int - Используй вместо
synchronizedдля простых счётчиков
- Используй вместо
-
Основа — CAS (Compare-And-Swap)
- Проверка и обновление в одну атомарную операцию
- Если не удалась — повтор
-
volatile внутри
- Гарантирует видимость изменений
- Но не атомарность самой операции
-
Когда использовать:
- Счётчики, флаги в многопоточной среде
- Простые значения (int, long, boolean)
- Когда нужна высокая производительность
-
Когда использовать
synchronizedвместо:- Сложные операции (несколько переменных)
- Когда нужна гарантия взаимного исключения для блока кода
Атомарные типы — это ключевой инструмент для написания эффективного многопоточного Java кода.