Ответ
Нет, volatile не обеспечивает атомарность операций.
Что гарантирует volatile:
- Видимость изменений: Гарантирует, что все потоки увидят самое актуальное значение переменной. Запись в
volatile-переменную происходит в основную память, минуя кэши процессоров. - Запрет переупорядочивания: Компилятор и процессор не могут переупорядочить операции с
volatile-переменной относительно других операций чтения/записи.
Почему операции не атомарны? Сложные операции, такие как инкремент (i++), состоят из нескольких шагов (чтение, изменение, запись). Поток может быть прерван между этими шагами, что приводит к "потере" обновлений.
Пример проблемы:
volatile int counter = 0;
// Операция НЕ атомарна
counter++; // Эквивалентно: int temp = counter; temp = temp + 1; counter = temp;
Атомарные альтернативы:
- Классы из
java.util.concurrent.atomic:AtomicInteger atomicCounter = new AtomicInteger(0); atomicCounter.incrementAndGet(); // Атомарная операция -
Синхронизация:
private final Object lock = new Object(); private int counter = 0; public void increment() { synchronized(lock) { counter++; } }
Ответ 18+ 🔞
А, слушай, смотри, тут народ часто путается, как пьяный в луже. volatile — это не волшебная палочка, которая делает всё атомарным, нет, блядь.
Что он реально делает, этот volatile?
- Видимость, ёпта! Гарантирует, что если один поток впендюрил новое значение в переменную, то все остальные потоки это увидят сразу, а не будут до скончания века читать свою закэшированную копию из какого-нибудь регистра. Прямая запись в основную память, минуя все эти буферы.
- Порядок, сука! Компилятору и процессору запрещено переставлять операции с этой переменной туда-сюда как попало относительно других операций чтения/записи. Это чтобы не было неожиданных сюрпризов.
А в чём подвох-то? Да в том, что сложная операция — это не один шаг! Возьмём этот проклятый инкремент i++. Он же из трёх действий состоит: прочитать значение, прибавить к нему единицу, записать обратно. И вот представь: поток А прочитал, скажем, 5. Только собрался записать 6, как его вытеснили. Поток Б успевает прочитать всё те же 5, прибавить и записать 6. Потом поток А возвращается и тупо перезаписывает тем же 6. И где наше обновление? Потерялось, в рот меня чих-пых! volatile тут нихуя не поможет, потому что прерваться могут между этими шагами.
Вот, смотри, наглядный пиздец:
volatile int counter = 0;
// Это НЕ атомарно, запомни раз и навсегда!
counter++; // По факту: int temp = counter; temp = temp + 1; counter = temp;
Так что же делать, если надо по-честному?
- Вот эти ребята из
java.util.concurrent.atomic— красавчики: Они делают это на уровне процессора.AtomicInteger atomicCounter = new AtomicInteger(0); atomicCounter.incrementAndGet(); // Вот это уже атомарная операция, без всяких там. -
Старая добрая синхронизация: Ну, если очень хочется.
private final Object lock = new Object(); private int counter = 0; public void increment() { synchronized(lock) { // Заперлись и делаем спокойно counter++; } }
Короче, volatile — это про видимость и порядок, а не про атомарность. Не путай, а то будет тебе волнение ебать и терпения ноль ебать при отладке.