Да, действительно: volatile чтения может конфликтовать таким образом, что два потока увидят нулевое значение ссылки, и будет выполнена двойная реализация.
Вам также нужна инициализация с двойной скобкой и volatile.Это потому, что когда instance
становится ненулевым, вы не синхронизируете другие потоки ни перед чем, читая его - сначала if
просто заставляет их идти дальше, возвращая значение unsynchronized
(даже если инициализирующий поток не экранируетсинхронизированный блок еще), это может привести к последующему чтению потока не инициализированной переменной из-за отсутствия синхронизации.Синхронизация для правильной работы должна выполняться каждым потоком, обращающимся к данным, которыми он управляет, DCL пропускает синхронизацию после инициализации, что является неправильным.Вот почему вам нужен дополнительный volatile для работы DCL, тогда volatile будет гарантировать, что вы прочитали инициализированное значение.
Нет такой вещи, как разделение кэша процессора, чтение и запись видны сразу, но естьперестановки команд, поэтому в пользу оптимизации процессор может вызвать некоторые инструкции позже, если ему не нужны их результаты сразу.Весь смысл синхронизации и изменчивости состоит в том, чтобы не изменять порядок команд для потоков, обращающихся к ним.Таким образом, если что-то синхронизировано и заявлено как выполненное в коде, это действительно сделано, и другие потоки могут получить к нему безопасный доступ.В этом вся суть дела перед гарантией.
Суммируя это вместе: без надлежащей синхронизации процессор может инициализировать ссылку на instance
как ненулевую, но instance
не может быть полностью инициализирована внутри, поэтому последующаячтение потока может читать неинициализированный объект и вести себя неправильно из-за этого.