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

Являются ли атомарные переменные примитивов volatile

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

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

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

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

Атомарные переменные и volatile

Да, атомарные переменные примитивов (Atomic классы) обладают гарантиями, превосходящими volatile. Они обеспечивают не только видимость изменений между потоками (как volatile), но и атомичность операций, которые volatile не гарантирует.

Сравнение volatile и Atomic

volatile гарантирует:

  • Видимость (visibility) — изменение видно всем потокам
  • Порядок (ordering) — некоторые гарантии упорядочения операций
  • НЕ гарантирует атомичность составных операций

Atomic гарантирует:

  • Видимость (visibility)
  • Порядок (ordering)
  • Атомичность операций
  • Дополнительные операции: CAS (Compare-And-Swap), increment, decrement и т.д.

Проблема с volatile

public class VolatileProblem {
    private volatile int counter = 0;  // volatile!
    
    public void increment() {
        counter++;  // НЕ атомично!
        // Это три операции: read, increment, write
        // Между read и write другой поток может изменить значение
    }
}

// Результат: с несколькими потоками counter будет меньше чем количество increment() вызовов
public class VolatileCounterTest {
    public static void main(String[] args) throws InterruptedException {
        VolatileProblem problem = new VolatileProblem();
        
        Thread[] threads = new Thread[5];
        for (int i = 0; i < 5; i++) {
            threads[i] = new Thread(() -> {
                for (int j = 0; j < 1000; j++) {
                    problem.increment();
                }
            });
        }
        
        for (Thread t : threads) t.start();
        for (Thread t : threads) t.join();
        
        // Вывод не всегда будет 5000
        System.out.println(problem.counter);
    }
}

Решение с Atomic

import java.util.concurrent.atomic.AtomicInteger;

public class AtomicSolution {
    private AtomicInteger counter = new AtomicInteger(0);
    
    public void increment() {
        counter.incrementAndGet();  // Атомично!
    }
    
    public int getCounter() {
        return counter.get();
    }
}

// Результат: всегда будет 5000
public class AtomicCounterTest {
    public static void main(String[] args) throws InterruptedException {
        AtomicSolution solution = new AtomicSolution();
        
        Thread[] threads = new Thread[5];
        for (int i = 0; i < 5; i++) {
            threads[i] = new Thread(() -> {
                for (int j = 0; j < 1000; j++) {
                    solution.increment();
                }
            });
        }
        
        for (Thread t : threads) t.start();
        for (Thread t : threads) t.join();
        
        System.out.println(solution.getCounter());  // Всегда 5000
    }
}

Как работают Atomic классы

Атомарные классы используют Compare-And-Swap (CAS) операцию на уровне CPU:

public class AtomicIntegerSimplified {
    private volatile int value;  // Внутри Atomic используется volatile
    
    public int getAndIncrement() {
        int oldValue;
        do {
            oldValue = value;  // 1. Read текущее значение
            // 2. CAS: если value равно oldValue, то установить value = oldValue + 1
            // Если другой поток изменил value, цикл повторится
        } while (!compareAndSet(oldValue, oldValue + 1));
        return oldValue;
    }
    
    private synchronized boolean compareAndSet(int expected, int update) {
        if (value == expected) {
            value = update;
            return true;
        }
        return false;
    }
}

Настоящая реализация использует sun.misc.Unsafe с нативными CAS операциями для лучшей производительности.

Основные Atomic классы

1. AtomicInteger, AtomicLong, AtomicBoolean, AtomicReference

import java.util.concurrent.atomic.*;

public class AtomicTypesExample {
    // Для примитивов
    private AtomicInteger atomicInt = new AtomicInteger(0);
    private AtomicLong atomicLong = new AtomicLong(0L);
    private AtomicBoolean atomicBool = new AtomicBoolean(false);
    
    // Для объектов
    private AtomicReference<String> atomicString = new AtomicReference<>("initial");
    
    public void demonstrateAtomicOperations() {
        // Get and Set
        int oldValue = atomicInt.getAndSet(10);
        
        // Increment and Decrement
        atomicInt.incrementAndGet();
        atomicInt.decrementAndGet();
        
        // Add
        atomicInt.addAndGet(5);
        
        // Compare and Set
        boolean success = atomicInt.compareAndSet(16, 20);
        
        // Get
        int value = atomicInt.get();
    }
}

2. AtomicIntegerArray, AtomicLongArray, AtomicReferenceArray

import java.util.concurrent.atomic.AtomicIntegerArray;

public class AtomicArrayExample {
    private AtomicIntegerArray array = new AtomicIntegerArray(10);
    
    public void demonstrateAtomicArray() {
        // Установить значение в позицию 5
        array.set(5, 100);
        
        // Атомично получить и увеличить
        int oldValue = array.getAndIncrement(5);
        
        // CAS для элемента массива
        array.compareAndSet(5, 101, 200);
    }
}

3. AtomicMarkableReference и AtomicStampedReference

Для решения problem ABA (рассинхронизация значений при потокобезопасности):

import java.util.concurrent.atomic.AtomicStampedReference;

public class ABAProtectionExample {
    private AtomicStampedReference<String> ref = 
        new AtomicStampedReference<>("initial", 0);
    
    public void demonstrateStampedReference() {
        String expectedValue = ref.getReference();
        int[] stampHolder = new int[1];
        ref.get(stampHolder);
        int expectedStamp = stampHolder[0];
        
        // Сравниваем и значение, и версию (stamp)
        boolean success = ref.compareAndSet(
            expectedValue, "new_value",
            expectedStamp, expectedStamp + 1
        );
    }
}

Volatile vs Atomic: когда что использовать

Используй volatile для:

  • Простых флагов синхронизации
  • Полей, которые часто читаются, редко пишутся
  • Когда нужна только видимость, без атомичности
public class ThreadControl {
    private volatile boolean shouldStop = false;
    
    public void run() {
        while (!shouldStop) {
            // Work
        }
    }
    
    public void stop() {
        shouldStop = true;
    }
}

Используй Atomic для:

  • Счётчиков, которые часто обновляются
  • Любых операций, требующих атомичности
  • Конкурирующего доступа к числовым значениям
  • Безопасной замены ссылок
public class StatsCollector {
    private AtomicInteger successCount = new AtomicInteger(0);
    private AtomicInteger failureCount = new AtomicInteger(0);
    
    public void recordSuccess() {
        successCount.incrementAndGet();
    }
    
    public void recordFailure() {
        failureCount.incrementAndGet();
    }
}

Внутренняя реализация Atomic

public class AtomicInteger extends Number implements java.io.Serializable {
    private volatile int value;  // Базируется на volatile
    
    // Сравниваем текущее значение с ожидаемым и устанавливаем новое
    public final boolean compareAndSet(int expect, int update) {
        return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
    }
    
    // Атомично получаем и увеличиваем
    public final int getAndIncrement() {
        return unsafe.getAndAddInt(this, valueOffset, 1);
    }
}

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

AtomicInteger немного медленнее, чем обычная volatile переменная при чтении (потому что CAS дороже), но для записи и составных операций он значительно эффективнее, чем synchronized.

Операция | volatile | synchronized | AtomicInteger
---------|----------|--------------|---------------
get      | ~1ns     | ~5ns         | ~1ns
set      | ~1ns     | ~5ns         | ~3ns
inc++    | UNSAFE   | ~10ns        | ~3ns

Ключевые моменты

  • Atomic классы используют volatile внутри
  • Atomic классы гарантируют атомичность операций, volatile не гарантирует
  • CAS (Compare-And-Swap) — механизм обеспечения атомичности
  • Для счётчиков и операций всегда используй Atomic, а не volatile
  • AtomicInteger/Long более эффективны, чем synchronized для простых операций
  • Для сложных операций над несколькими переменными используй synchronized или ReentrantLock
  • Atomic классы не блокируют потоки, используя оптимистичный подход (CAS)