Потокобезопасный singleton

299
26 ноября 2016, 19:17

Вот потокобезопасный 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. От чего это спасает ?

Answer 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, никогда не кешируется в память потока, то есть она в любой момент времени в любом потоке будет иметь одинаковое (актуальное) значение (если один поток меняет ее значение, то это значение сразу же доступно в других потоках).

Answer 2

Потому что чтение при сравнении

if (instance == null) {

происходит вне synchronized блока, а так уже и sideeffects могут быть, например, созданный объект в heap, но с ещё не вызванным конструктором или кешированное значение null. Модель памяти JDK 5.0 и выше требует этого, чтобы поведение многопоточных программ было предсказуемым.

READ ALSO
О выводе графики

О выводе графики

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

243
Работа с Picasso(Android), загрузка в ресурсы

Работа с Picasso(Android), загрузка в ресурсы

Подскажите, как можно загрузить изображение не в ImageView а в папку с ресурсами либо установить загруженное изображение сразу в качестве фона...

319
проблема с awt.graphics

проблема с awt.graphics

При попытке вызвать paintComponent с передачей аргументов происходит ошибка, если я удаляю аргументы и назначаю внутри paintComponent свои все работает

249
Используется ли сейчас vector или только stack? [закрыто]

Используется ли сейчас vector или только stack? [закрыто]

И если используется то часто ли? PS

239