Почему volatile не делает i++ атомарным

Этим вопросом проверяют, понимает ли кандидат разницу между видимостью и атомарностью. Многие путают эти два свойства и отвечают «volatile же для многопоточности» — этого недостаточно.

volatile гарантирует happens-before между записью и последующим чтением поля: если поток A записал значение, а поток B потом его прочитал, B увидит именно это значение и всё, что было записано до него в потоке A. Это решает проблему видимости — устаревших значений из кэша процессора.

Но составная операция вроде counter++ — это три шага: прочитать, увеличить, записать. Между чтением и записью может влезть другой поток. volatile не превращает эти три шага в одну неделимую операцию, он только гарантирует, что каждый отдельный шаг видит актуальное значение.

private volatile int counter = 0;

void increment() { counter++; // read-modify-write, гонка возможна }

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

Дальше спросят: а что если заменить на synchronized-метод? Ответ — сработает, потому что synchronized даёт и видимость, и атомарность блока. Следующий вопрос — как AtomicInteger достигает атомарности без блокировки. Ответ — через compare-and-swap: CPU-инструкция, которая атомарно сравнивает и обновляет значение, а при неудаче операция повторяется в цикле.

volatile отвечает за то, что поток увидит свежее значение. За то, что операция выполнится целиком без вмешательства, отвечают atomic-классы, synchronized или явные локи — это разные задачи, и одно не заменяет другое.

Тренажёр: 600 вопросов, мок с таймером, план повторов

senior·base — что спрашивают на самом деле


В этом посте были ссылки, но мы их удалили по правилам Сетки