Как частичный объект может быть создан в синхронизированном блоке двойной проверки блокировки? [dубликат]

Вы связались с ИТ-отделом своего университета? У моей жены была аналогичная проблема, и мы предположили, что это проблема Ubuntu. Однако я думаю, что сеть активно блокировала ее связь. В то время мы этого не знали, но конкретная сеть Wi-Fi, доступная моей жене, доступна только компьютерам университета или компьютерам, на которых установлено «безопасное» изображение для Windows или Mac. Если это общая сеть wi-fi, доступная студентам и т. Д., Пожалуйста, не обращайте внимания. Я думал, что я опубликую это, хотя, если вы являетесь сотрудником, пытающимся получить доступ к сети Wi-Fi сотрудника.

51
задан 17 October 2016 в 21:17

4 ответа

Если у вас его нет, второй поток может попасть в синхронизированный блок после первого установленного значения null, а ваш локальный кеш все равно будет считать его нулевым.

Первый не для правильности (если бы вы были правы, что это было бы самовыражением), а скорее для оптимизации.

1
ответ дан 15 August 2018 в 16:11
  • 1
    Согласно случаям - до определения, после того, как один поток освобождает блокировку, другой поток получает блокировку, а затем последний может видеть предыдущее изменение. Если это так, я не думаю, что ключевое слово volatile необходимо. Можете ли вы объяснить это более подробно – Qin Dong Liang 5 July 2018 в 05:32
  • 2
    @QinDongLiang Если переменная не является изменчивой, второй поток может использовать кешированное значение в своем стеке. Будучи изменчивым, он возвращается к источнику, чтобы получить правильное значение. Конечно, он должен делать это при каждом доступе и, следовательно, может иметь удар производительности (но будем честными, если только он не находится в суперкритической петле, это, вероятно, не самое худшее в вашей системе ...) – corsiKa 5 July 2018 в 06:43

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

Без volatile компилятор может оптимизировать доступ к памяти и сохранить его значение в регистр, поэтому только первое использование переменной считывает фактическую ячейку памяти, содержащую переменную. Это проблема, если переменная изменена другим потоком между первым и вторым доступом; первый поток имеет только копию первого (предварительно измененного) значения, поэтому второй оператор if проверяет устаревшую копию значения переменной.

0
ответ дан 15 August 2018 в 16:11
  • 1
    -1 Сегодня я теряю свои очки репутации :) Причина real в том, что есть кеш памяти, смоделированный как локальная память потока. Порядок, в котором локальная память очищается до основного, не определена, т. Е. Если у вас нет отношений -before , например, с помощью volatile. Регистры не имеют ничего общего с неполными объектами и проблемой DCL. – alf 22 October 2011 в 03:14
  • 2
    Ваше определение volatile слишком узкое - если все это было волатильно, двойная проверенная блокировка отлично работала бы в & lt; Java5. volatile вводит барьер памяти, делающий определенное переупорядочение незаконным - без этого, даже если мы никогда не будем читать устаревшие значения из памяти, он все равно будет небезопасным. Редактировать: alf избил меня до этого, не должен был получить хороший чай;) – Voo 22 October 2011 в 03:18
  • 3
    @TimBender, если singleton содержит изменяемое состояние, flushing это не имеет никакого отношения к ссылке на сам синглтон (ну, есть косвенная ссылка, поскольку доступ к ссылке volatlie к singleton делает ваш поток повторно -read main memory - но это вторичный эффект, а не причина проблемы :)) – alf 22 October 2011 в 03:18
  • 4
    @alf, ты прав. И фактически создание volatile vol не помогает, если внутреннее состояние изменено, поскольку флеш происходит только в том случае, если сама ссылка изменена (например, при создании массивов / списков volatile ничего не делает для содержимого). Мел это до мозгового пердуна. – Tim Bender 22 October 2011 в 03:22
  • 5
    Согласно случаям - до определения, после того, как один поток освобождает блокировку, другой поток получает блокировку, а затем последний может видеть предыдущее изменение. Если это так, я не думаю, что ключевое слово volatile необходимо. Не могли бы вы объяснить это подробнее @Tim Bender – Qin Dong Liang 5 July 2018 в 05:35

Неустойчивое чтение не очень дорого само по себе.

Вы можете создать тест для вызова getInstance() в узком цикле, чтобы наблюдать влияние изменчивого чтения; однако этот тест нереалистичен; в такой ситуации программист обычно вызывал getInstance() один раз и кэшировал экземпляр для продолжительности использования.

Другой impl с помощью поля final (см. wikipedia). Для этого требуется дополнительное чтение, которое может стать дороже, чем версия volatile. Версия final может быть быстрее в узком цикле, однако этот тест является спорным, как ранее утверждалось.

1
ответ дан 15 August 2018 в 16:11

Как цитируется @irreputable, volatile не дорогая. Даже если это дорого, согласованность должна быть отдана приоритету над производительностью.

Существует еще один чистый элегантный способ для Lazy Singletons.

public final class Singleton {
    private Singleton() {}
    public static Singleton getInstance() {
        return LazyHolder.INSTANCE;
    }
    private static class LazyHolder {
        private static final Singleton INSTANCE = new Singleton();
    }
}

Исходная статья: Инициализация-по-запросу_holder_idiom из wikipedia

В разработке программного обеспечения Инициализация по требованию (дизайн шаблона) идиома - это ленивый загруженный синглтон. Во всех версиях Java идиома позволяет безопасную, очень параллельную ленивую инициализацию с хорошей производительностью

. Поскольку для инициализации у класса нет никаких static переменных, инициализация завершается тривиально.

Определение статического класса LazyHolder внутри него не инициализируется до тех пор, пока JVM не определит, что LazyHolder должен быть выполнен.

Статический класс LazyHolder выполняется только тогда, когда статический метод getInstance вызывается в классе Singleton, и в первый раз это произойдет, JVM будет загружать и инициализировать класс LazyHolder.

Это решение является потокобезопасным, не требуя специальных языковых конструкций (т. е. volatile или synchronized).

10
ответ дан 15 August 2018 в 16:11

Другие вопросы по тегам:

Похожие вопросы: