Что делает volatile?

volatile – ключевое слово для работы с многопоточностью. Не то же самое, что volatile в C++, не обязано делать что-либо с кэшем процессора. Оказывает на поле объекта ровно два эффекта.

Во-первых, чтение/запись такого поля становятся атомарными. Это применение актуально только для long и double, и не на всех платформах. Для остальных типов полей это верно и так.

Второй и самый интересный эффект – пара событий запись-чтение для такого поля являются synchronization actions. Значит, между ними существует отношение happens-before. Это значит, что существует гарантия, что произошедшее в памяти до записи будет видно после чтения. То есть будут успешно прочитаны значения, записанные в другие переменные.

Для полного понимания темы рекомендуется к просмотру доклад Алексея Шипилёва и документация. Лучше всего эффект volatile иллюстрирует задача из этого доклада, которую часто и дают в качестве этого вопроса. Вопрос – что выведет данный код:
int a; int b;

// thread 1:
a = 1;
b = 2;

// thread 2:
System.out.print(b);
System.out.print(a);

Трюк в том, что помимо очевидных 21 (поток 2 отработал после 1), 00 (поток 2 отработал до 1, переменные еще не инициализированы) и 01 (поток 2 сработал между записями), может быть и неожиданные 20. Дело в том, что для операторов одного потока действует program order, он гарантирует хотя бы видимость правильной последовательности операций. Между потоками необходим «мост» из happens-before. Его даст применение модификатора volatile к переменной b, неожиданный результат 20 будет исключен.

Этот эффект используется для получения простой и дешевой адаптации программы к многопоточной среде без использования сложных и ошибкоопасных техник блокировок и синхронизаций.