почему java двойная проверка блокировки синглтона должна использовать ключевое слово volatile? - PullRequest
1 голос
/ 03 февраля 2020

Я видел несколько похожих вопросов, но у меня все еще есть некоторые заблуждения.
код здесь:

private volatile static DoubleCheckSingleton instance;

private DoubleCheckSingleton() {}
public static DoubleCheckSingleton getInstance(){

    if(instance==null){ //first

        synchronized (DoubleCheckSingleton.class){

            if(instance==null){  // second
                instance=new DoubleCheckSingleton();
            }

        }

    }

    return instance;

}

В этом вопросе Почему volatile используется в двойной проверке блокировки, он говорит, что без ключевого слова volatile поток может назначить переменную экземпляра до завершения работы конструктора, поэтому другой поток может увидеть наполовину созданный объект, что может вызвать серьезные проблемы.

Но я не Я не понимаю, насколько изменчиво может решить проблему. Volatile используется для обеспечения видимости, поэтому, когда поток A назначает полуструктурированный объект переменной экземпляра, другой поток может сразу увидеть изменение, что ухудшает ситуацию.

Как решает volatile Проблема, кто-нибудь, пожалуйста, объясните мне. Спасибо!

Ответы [ 3 ]

2 голосов
/ 03 февраля 2020

поток может назначить переменную экземпляра до завершения конструктора

На самом деле это не так. Назначение прямо в примере кода:

instance=new DoubleCheckSingleton()

Очевидно, что поток, который выполняет это назначение, не может выполнить назначение до того, как будет возвращен вызов конструктора.

Проблема в том, когда два разных потока работают на двух разных процессорах без какой-либо синхронизации, они не обязательно будут согласовывать порядок, в котором выполняются назначения. Таким образом, несмотря на то, что поток A назначил поля нового объекта (внутри вызова new DoubleCheckSingleton()) до , он назначил instance, поток B потенциально мог видеть эти назначения не по порядку. Поток B мог видеть присваивание instance, прежде чем он увидел некоторые другие вещи, которые new DobuleCheckSingleton() сделал.

Объявление instance как volatile синхронизирует потоки. volatile гарантирует, что все поток A проделал до того, как присвоенная ему переменная volatile станет видимой для потока B, когда поток B получит значение переменной volatile.

1 голос
/ 04 февраля 2020

Двойная проверка блокировки потратила много времени в качестве антишаблона, согласно Java Concurrency In Practice et. и др. В то время они предложили использовать что-то вроде паттерна Lazy Holder.

https://en.wikipedia.org/wiki/Initialization-on-demand_holder_idiom

Брайан Гетц написал хорошую статью о том, как DCL был нарушен здесь https://www.javaworld.com/article/2074979/double-checked-locking--clever--but-broken.html

Как указал мне Стивен C, поскольку обновление модели памяти Java будет работать должным образом, хотя, несмотря на это, я думаю, что Lazy Holder по-прежнему хорошая альтернатива. Он чист в применении и не требует применения летучих веществ. Неправильная реализация DCL приведет к трудностям поиска ошибок.

0 голосов
/ 03 февраля 2020

Другими словами, предположим, что у нас есть два идеально гоночных потока:

  • THREAD 1: "if instance! = Null" ... хорошо, это null.
  • THREAD 2: "if instance! = Null" ... хорошо, это ноль.
  • THREAD 1: создает новый "instance"
  • THREAD 2: STOMPS ON значение, только что созданное потоком 1.

... и, вполне вероятно, ни один из потоков в настоящее время не знает, что что-то не так. Вполне вероятно, что они оба думают, что у них есть DoubleCheckSingleton и используют их так, как они должны… ни один из них не осознает, что есть два. (Так что, откиньтесь на спинку кресла и смотрите данные - структуры рушатся влево и вправо, когда ваше приложение упало в землю.)

Еще хуже, это именно та ошибка, которая "случается однажды в синей луне". То есть, это никогда не происходит ни на одном компьютере разработчика, но это происходит через пять минут после развертывания в рабочей среде ... ; -)

Ключевое слово volatile в основном говорит, что "это место хранения, само по себе является общим ресурсом." Что это такое.

Теперь также: я думаю, что наилучшей практикой является инициализация всех ваших объектов синхронизации до того, как вы начнете запускать потоки или подпроцессы. Все эти сущности теперь могут ссылаться на эти уже существующие объекты и могут использовать их, но никто из этих игроков никогда не создает их.

...