Вопрос с собеседования · Java

Почему в double-checked locking поле instance обязано быть volatile?

SeniorJVM и сборка мусора
Коротко

new Singleton() — три шага: выделить память, выполнить конструктор, присвоить ссылку. Без volatile присвоение может опередить конструктор, и второй поток на первой проверке (вне synchronized) получит недостроенный объект.

  • volatile запрещает это переупорядочивание и даёт happens-before
  • Проще и надёжнее — holder-класс (ленивая инициализация загрузчиком) или enum
  • В Java 1.4 и раньше volatile не спасал — модель памяти исправлена в Java 5

А ещё Локальная переменная уменьшает число volatile-чтений

Подробно

Разложи new Singleton() на три шага: выделить память, отработать конструктор, присвоить ссылку — операция не атомарна. Без volatile модель памяти разрешает переупорядочивание, при котором ссылка присваивается до завершения конструктора. Тогда второй поток на первой (небезопасной) проверке видит не нулевую ссылку, пропускает синхронизацию и получает частично инициализированный объект — с полями по умолчанию вместо реальных значений. volatile закрывает дыру, потому что запись в него happens-before последующего чтения и запрещает такое переупорядочивание. Сильное завершение — что в реальном коде проще взять enum-синглтон или static holder, где ленивость обеспечивают правила инициализации классов, и DCL писать вручную не нужно.

Пример

private static volatile Singleton instance;
static Singleton get() {
    Singleton r = instance;
    if (r == null) {
        synchronized (Singleton.class) {
            r = instance;
            if (r == null) instance = r = new Singleton();
        }
    }
    return r;
}

// проще:
class Holder { static final Singleton INSTANCE = new Singleton(); }

Что хотят услышать

  • Три шага создания и опасное переупорядочивание.
  • Роль volatile.
  • Альтернативы: holder и enum.

Частые ошибки

  • Объяснять только через видимость, не упоминая переупорядочивание.
  • Считать, что достаточно synchronized на второй проверке.

Проверьте себя

Один верный вариант. Подумайте, потом откройте ответ.

Показать варианты и ответ
  • ✗volatile делает блок synchronized ненужным, и проверку можно проводить один раз
  • ✗Без volatile поле кэшируется в регистре навсегда и второй поток создаст второй экземпляр
  • ✗volatile нужен только для сериализации синглтона, чтобы при чтении из потока не появился второй объект
  • ✓Иначе ссылка может стать видна раньше, чем отработал конструктор

Что спросят дальше

  • Почему static holder ленив без единой блокировки?
  • Что даёт final-поле в терминах безопасной публикации?

Разбор темы

Связанные вопросы

Партнёр платформы

Разбор собеседования с sotto

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

Разбор собеседования