Операция создания объекта в Java не является атомарной.
Рассмотрим (один из возможных) пример с выполнением операции создания объекта и двумя потоками:
Поток A входит в метод getInstance(), в этот момент времени instance == null и он входит в синхронизированный блок, в котором тоже instance == null и начинает создание объекта Singleton. Сначала выделяется память под объект, потом этот объект инициализируется ссылкой на выделенную область памяти. В этот момент времени Поток B заходит в метод getInstance() видит, что instance != null и начинает использовать уже существующий, но еще не донца сконструированный объект (так как его поля еще не инициализированы).
Объявление поля instance как volatile (JDK 5+) устанавливает отношение happens before между инициализацией объекта instance Потоком A и возвратом объекта instance Потоку B.
Иными словами, объявление поля instance как volatile гарантирует, что поток В прочитает уже полностью сконструированный объект instance.
UPD. Из комментариев @Roman еще одна причина необходимости использования volatile:
Без volatile есть ещё одна проблема: если первая проверка if (instance == null) увидит не null, последующий return instance может увидеть null, в результате метод вернёт null.
Если нет корректной синхронизации, то чтение, которое идёт "позже" может увидеть значение, которое было "раньше", т.к. JMM не запрещает переупорядочивать такие чтения.
Переменная, объявленная volatile, никогда не кешируется в память потока, то есть она в любой момент времени в любом потоке будет иметь одинаковое (актуальное) значение (если один поток меняет ее значение, то это значение сразу же доступно в других потоках).
volatileи даже знаю про кэш процессора. Вопрос в том, зачем нам делатьinstanceзащищенным от кеширования, если вторая проверка происходит внутри залоченного монитора ? – faoxis Nov 14 '16 at 19:03