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

Какой принцип реализуют атомарные примитивы?

3.0 Senior🔥 141 комментариев
#JVM и управление памятью#Многопоточность

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

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

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

Атомарные примитивы: принцип Non-Blocking Synchronization

Атомарные примитивы (Atomic classes) в Java реализуют принцип Compare-And-Swap (CAS) — неблокирующий механизм синхронизации, который обеспечивает потокобезопасность без использования блокировок (locks).

Основной принцип: Compare-And-Swap (CAS)

Compare-And-Swap — это операция, которая:

  1. Читает текущее значение переменной
  2. Сравнивает его с ожидаемым значением
  3. Если совпадает → атомарно записывает новое значение
  4. Если не совпадает → отказывает в запись и возвращает текущее значение
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.

Какой принцип реализуют атомарные примитивы? | PrepBro