Какой принцип реализуют атомарные примитивы?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Атомарные примитивы: принцип Non-Blocking Synchronization
Атомарные примитивы (Atomic classes) в Java реализуют принцип Compare-And-Swap (CAS) — неблокирующий механизм синхронизации, который обеспечивает потокобезопасность без использования блокировок (locks).
Основной принцип: Compare-And-Swap (CAS)
Compare-And-Swap — это операция, которая:
- Читает текущее значение переменной
- Сравнивает его с ожидаемым значением
- Если совпадает → атомарно записывает новое значение
- Если не совпадает → отказывает в запись и возвращает текущее значение
CAS(address, expectedValue, newValue):
if (memory[address] == expectedValue) {
memory[address] = newValue
return true
}
return false
Это происходит АТОМАРНО на уровне CPU — без возможности прерывания другим потоком.
Где реализуется: Unsafe класс
Под капотом атомарные примитивы используют класс sun.misc.Unsafe, который предоставляет доступ к CAS операциям:
// Внутри AtomicInteger
public class AtomicInteger extends Number {
private static final Unsafe unsafe = Unsafe.getUnsafe();
private static final long valueOffset;
private volatile int value;
public final boolean compareAndSet(int expect, int update) {
// CAS операция на уровне CPU
return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}
}
Атомарные примитивы в Java
В пакете java.util.concurrent.atomic есть классы:
// Для примитивов
AtomicBoolean // boolean
AtomicInteger // int
AtomicLong // long
AtomicReference // любой объект T
// С массивами
AtomicIntegerArray
AtomicLongArray
AtomicReferenceArray
// С полями класса
AtomicIntegerFieldUpdater
AtomicLongFieldUpdater
AtomicReferenceFieldUpdater
Пример: AtomicInteger
import java.util.concurrent.atomic.AtomicInteger;
public class CounterExample {
// Потокобезопасный счетчик БЕЗ synchronized
private AtomicInteger counter = new AtomicInteger(0);
public void increment() {
// Атомарная операция
counter.incrementAndGet(); // count++ + сразу видно другим потокам
}
public int getCount() {
return counter.get();
}
public static void main(String[] args) throws InterruptedException {
CounterExample counter = new CounterExample();
// 10 потоков инкрементируют счетчик
Thread[] threads = new Thread[10];
for (int i = 0; i < 10; i++) {
threads[i] = new Thread(() -> {
for (int j = 0; j < 1000; j++) {
counter.increment();
}
});
threads[i].start();
}
for (Thread t : threads) {
t.join();
}
// Гарантировано: 10000
System.out.println("Count: " + counter.getCount());
}
}
AtomicInteger vs synchronized
С synchronized (blocking):
private int counter = 0;
public synchronized void increment() { // Блокировка
counter++;
}
public synchronized int getCount() { // Блокировка
return counter;
}
// Проблема: если поток держит lock, другие ждут
// Thread 1: ----[LOCK]----
// |
// Thread 2: ----[WAIT]----
// |
// Thread 3: ----[WAIT]----
С AtomicInteger (non-blocking):
private AtomicInteger counter = new AtomicInteger(0);
public void increment() { // БЕЗ блокировки
counter.incrementAndGet(); // CAS в цикле
}
public int getCount() { // БЕЗ блокировки
return counter.get();
}
// Преимущество: нет ожидания
// Thread 1: ----[CAS]--CAS--[OK]
// |
// Thread 2: -----[CAS]--CAS--[OK]
// |
// Thread 3: ------[CAS]--[OK]
Как работает CAS в цикле
public class AtomicInteger {
private volatile int value;
public final int incrementAndGet() {
// CAS цикл (spin loop)
for (;;) { // Бесконечный цикл
int current = get();
int next = current + 1;
// Пытаемся обновить
if (compareAndSet(current, next)) {
return next; // Успех, выходим
}
// Если CAS провалился — повторяем (retry)
}
}
}
// Пример в action:
int value = 5;
// Thread 1: читает 5, пытается записать 6
// Thread 2: читает 5, пытается записать 6
// Thread 1 быстрее: CAS успешен, value = 6
// Thread 2: сравниваю 5 == 6? НЕТ. Перечитываю: 6
// Thread 2: пытаюсь записать 7. CAS успешен, value = 7
Важное свойство: Volatile
Все атомарные классы используют volatile переменные, что означает:
private volatile int value; // Каждый read берет актуальное значение
// Каждый write видно всем потокам
// Благодаря volatile:
// 1. Запреты переупорядочивания инструкций
// 2. Memory barrier перед и после операций
// 3. Немедленная видимость изменений другим потокам
Сложные атомарные операции
getAndSet:
AtomicInteger ai = new AtomicInteger(10);
// Атомарно: получить старое и установить новое
int oldValue = ai.getAndSet(20);
System.out.println("Old: " + oldValue); // 10
System.out.println("New: " + ai.get()); // 20
compareAndSet:
AtomicInteger ai = new AtomicInteger(10);
// Условное обновление
boolean success = ai.compareAndSet(10, 20);
System.out.println(success); // true (было 10, установили 20)
success = ai.compareAndSet(10, 30);
System.out.println(success); // false (уже 20, не 10)
updateAndGet (Java 8+):
AtomicInteger ai = new AtomicInteger(5);
// Применить функцию атомарно
int result = ai.updateAndGet(x -> x * 2 + 1);
System.out.println(result); // 11 (5 * 2 + 1)
accumulateAndGet (Java 8+):
AtomicInteger ai = new AtomicInteger(10);
// Аккумулировать значение
int result = ai.accumulateAndGet(5, Integer::sum);
System.out.println(result); // 15 (10 + 5)
AtomicReference для объектов
public class User {
String name;
int age;
public User(String name, int age) {
this.name = name;
this.age = age;
}
}
public class UserCache {
private AtomicReference<User> cache = new AtomicReference<>();
public void updateUser(User newUser) {
cache.set(newUser); // Потокобезопасная запись
}
public User getUser() {
return cache.get(); // Потокобезопасное чтение
}
public boolean replaceUser(User expectedUser, User newUser) {
// Условное обновление
return cache.compareAndSet(expectedUser, newUser);
}
}
AtomicIntegerFieldUpdater для полей класса
public class Counter {
volatile int count = 0; // ДОЛЖНО быть volatile!
}
public class CounterUpdater {
// Вместо wrapper класса, обновляем поле напрямую
static final AtomicIntegerFieldUpdater<Counter> updater =
AtomicIntegerFieldUpdater.newUpdater(Counter.class, "count");
public static void main(String[] args) {
Counter c = new Counter();
updater.incrementAndGet(c); // count++
updater.incrementAndGet(c); // count++
System.out.println(c.count); // 2
// Экономит память (нет wrapper объекта)
}
}
Performance: Atomic vs Synchronized
Low contention (мало конфликтов):
AtomicInteger >> synchronized (Atomic быстрее на 10-100x)
High contention (много конфликтов):
AtomicInteger ≈ synchronized (примерно одинаково)
Причина: при высокой контенции CAS циклы спинят впустую, эффективнее держать lock.
Когда использовать Atomic
Используй AtomicInteger/Long когда:
- Простые счетчики, флаги
- Низкая/средняя контенция
- Нужна микро-оптимизация
- Избежать deadlock-ов
Используй synchronized когда:
- Защита сложной логики с несколькими переменными
- Высокая контенция
- Нужна семантика monitor-а
Таблица атомарных операций
| Метод | Описание |
|---|---|
get() | Получить текущее значение |
set(value) | Установить значение |
getAndSet(newValue) | Получить старое, установить новое |
compareAndSet(expect, update) | Условное обновление |
incrementAndGet() | ++value |
decrementAndGet() | --value |
addAndGet(delta) | value += delta |
getAndIncrement() | value++ |
updateAndGet(func) | value = func(value) |
accumulateAndGet(x, func) | value = func(value, x) |
Заключение
Атомарные примитивы реализуют принцип Compare-And-Swap (CAS) — неблокирующую синхронизацию на уровне CPU.
Ключевые преимущества:
- Нет блокировок -> нет deadlock-ов
- Лучше performance при низкой контенции
- Простая семантика
- Всегда актуальные значения (volatile гарантирует)
Это один из краеугольных камней многопоточного программирования в Java.