Короткий ответ
volatile решает проблему видимости: чтение и запись volatile-переменной выполняются напрямую из основной памяти, а между записью одним потоком и чтением другим устанавливается отношение happens-before. Однако volatile не обеспечивает атомарность: составная операция «прочитать — изменить — записать» вроде counter++ остаётся неатомарной. Атомарные классы из java.util.concurrent.atomic дают атомарные операции без блокировок — на базе аппаратной инструкции CAS.
Как это работает подробнее
volatile идеален для флагов состояния вроде boolean running, которые один поток выставляет, а другой читает. При counter++ два потока могут одновременно прочитать старое значение и записать результат, «потеряв» одно из увеличений, — даже если поле объявлено volatile.
Атомарные классы AtomicInteger, AtomicLong, AtomicReference предоставляют методы getAndIncrement(), addAndGet(), compareAndSet(expected, newValue), реализованные на аппаратной инструкции CAS (Compare-And-Swap). CAS действует оптимистично: compareAndSet проверяет, равно ли текущее значение ожидаемому, и только тогда обменивает его на новое; если значение уже изменил другой поток, операция не выполняется и поток повторяет попытку.
Пример кода
import java.util.concurrent.atomic.AtomicInteger;
class VolatileVsAtomic {
private volatile int count; // видимость есть, атомарности нет
private final AtomicInteger atomicCount = new AtomicInteger(0);
void demo() {
count++; // не атомарно: возможны потерянные обновления
atomicCount.incrementAndGet(); // атомарно, без блокировок, на CAS
}
}Что использовать на практике
- volatile — для простых флагов состояния (running, ready), где нужна только видимость свежего значения.