Что такое 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). Создание объекта состоит из нескольких действий:
- Выделение памяти
- Инициализация полей (вызов конструктора)
- Присвоение ссылки переменной
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остаётся в силе.