#java
Вот потокобезопасный singleton: public class Singleton { private volatile static Singleton instance; private Singleton() {} public static Singleton getInstance() { if (instance == null) { synchronized (Singleton.class) { if (instance == null) { instance = new Singleton(); } } } return instance; } } Никак не пойму зачем ему volatile, если он и так лочится по Singleton.class. От чего это спасает ?
Ответы
Ответ 1
Операция создания объекта в 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, никогда не кешируется в память потока, то есть она в любой момент времени в любом потоке будет иметь одинаковое (актуальное) значение (если один поток меняет ее значение, то это значение сразу же доступно в других потоках).Ответ 2
Потому что чтение при сравнении if (instance == null) { происходит вне synchronized блока, а так уже и sideeffects могут быть, например, созданный объект в heap, но с ещё не вызванным конструктором или кешированное значение null. Модель памяти JDK 5.0 и выше требует этого, чтобы поведение многопоточных программ было предсказуемым.
Комментариев нет:
Отправить комментарий