Вопрос-ответ

Why is volatile used in double checked locking

Почему volatile используется при блокировке с двойной проверкой

Из книги "Шаблоны проектирования Head First" одноэлементный шаблон с блокировкой с двойной проверкой был реализован следующим образом:

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 используется. Разве volatile использование не противоречит цели использования блокировки с двойной проверкой, т. е. производительности?

Переведено автоматически
Ответ 1

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

Реальная проблема заключается в том, что Thread A может быть выделено пространство памяти для instance до завершения сборкиinstance. Thread B увидит это назначение и попытается его использовать. Это приводит к Thread B сбою, потому что используется частично сконструированная версия instance.

Ответ 2

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

Есть еще один чистый элегантный способ для ленивых одиночек.

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

Исходная статья : Initialization-on-demand_holder_idiom из википедии


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


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

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

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

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

Ответ 3

Ну, здесь нет блокировки с двойной проверкой производительности. Это сломанный шаблон.

Оставляя эмоции в стороне, volatile здесь потому, что без этого ко времени прохождения второго потока instance == null первый поток может еще не быть создан new Singleton(): никто не обещает, что создание объекта произойдет до присвоения instance любому потоку, кроме того, который фактически создает объект.

volatile в свою очередь устанавливает отношение "происходит до" между операциями чтения и записи и исправляет нарушенный шаблон.

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

Ответ 4

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

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

java