Gymterview
middle

Что такое double checked locking Singleton?

Double Checked Locking (DCL) — это паттерн ленивой инициализации Singleton, который оптимизирует производительность за счёт того, что блокировка захватывается только при первом создании экземпляра.

Проблема: простая синхронизация (synchronized getInstance()) работает, но каждый вызов getInstance() проходит через блокировку, даже когда объект уже создан. DCL решает это, проверяя наличие экземпляра дважды — до и после входа в синхронизированный блок.

Пример
class DoubleCheckedLockingSingleton {
    private static volatile DoubleCheckedLockingSingleton instance;

    static DoubleCheckedLockingSingleton getInstance() {
        DoubleCheckedLockingSingleton current = instance; // (1) Первая проверка — без блокировки
        if (current == null) {
            synchronized (DoubleCheckedLockingSingleton.class) {
                current = instance; // (2) Вторая проверка — под блокировкой
                if (current == null) {
                    instance = current = new DoubleCheckedLockingSingleton(); // (3) Создание
                }
            }
        }
        return current;
    }
}

Почему volatile обязателен? Без volatile возможна ситуация, при которой JVM переупорядочит инструкции при создании объекта (шаг 3). Создание объекта состоит из нескольких действий:

  1. Выделение памяти
  2. Инициализация полей (вызов конструктора)
  3. Присвоение ссылки переменной instance

Без volatile JVM может выполнить шаги в порядке 1 → 3 → 2. Тогда другой поток, выполняя проверку if (current == null), увидит ненулевую ссылку на не полностью сконструированный объект и начнёт его использовать.

volatile гарантирует:

  • Запрет переупорядочивания (запись в volatile-поле happens-before чтения из него).
  • Видимость: все потоки видят актуальное значение.

Зачем локальная переменная current? Обращение к volatile-полю дороже обращения к локальной переменной. Присвоив instance в локальную переменную, мы читаем volatile только один раз на «быстром пути» (когда объект уже создан), что улучшает производительность.

Аналогия: DCL — это как проверка запертой двери. Сначала вы смотрите (без ключа): если дверь открыта — входите сразу. Если закрыта — достаёте ключ (синхронизация), открываете, и снова проверяете — вдруг кто-то открыл, пока вы искали ключ.

На собеседовании классический вопрос: «Почему DCL без volatile сломан?» Ответ должен включать упоминание переупорядочивания инструкций JVM и happens-before. Также стоит упомянуть, что эта проблема была частично решена в JDK 1.5 (новая спецификация JMM), но рекомендация использовать volatile остаётся в силе.