volatile в Java: что это и как отвечать на собеседовании
Что делает volatile
У каждого потока есть свой кэш. Обычную переменную поток читает из кэша и может не видеть, что её уже поменял другой поток. volatile даёт две гарантии:
- Видимость. Запись в volatile-переменную сразу видна другим потокам — они читают свежее значение из основной памяти, а не устаревшее из кэша.
- Запрет переупорядочивания (happens-before). Компилятор и процессор не переставят операции через volatile-доступ.
Аналогия: обычная переменная — заметка в твоём блокноте (смотришь в свою устаревшую копию).
volatile — правило «всегда сверяйся с общей доской на стене», где актуальное значение видят все.volatile ≠ атомарность (главная ловушка)
Частая ошибка — считать, что volatile делает операцию потокобезопасной. Классика:
volatile int x; x++; // НЕ атомарно
x++ — это три операции: прочитать, увеличить, записать. Между чтением и записью другой поток может вклиниться, и одно увеличение потеряется. Для счётчика нужен AtomicInteger (CAS) или synchronized.
Правило:
volatile годится, когда пишет один поток, а читают многие (флаг, сигнал остановки, публикация ссылки). Как только «прочитал → изменил на основе прочитанного» — нужен Atomic* или блокировка.Где volatile достаточно
- Флаг остановки:
volatile boolean running— один пишетfalse, цикл видит и завершается. - Double-checked locking: ссылку на singleton делают
volatile, чтобы не увидеть частично сконструированный объект. - Публикация неизменяемого объекта — записал ссылку в volatile-поле, читатели видят готовый объект.
volatile vs synchronized vs Atomic
volatile
Только видимость и порядок для одной переменной. Без взаимного исключения. Почти без накладных расходов.
AtomicInteger / AtomicReference
Атомарные операции над одной переменной через CAS:
incrementAndGet(), compareAndSet(). Для счётчиков и lock-free.synchronized / Lock
Взаимное исключение + видимость для составного состояния. Дороже, но универсальнее.