Ответ
Double-Checked Locking (DCL) — это паттерн оптимизации для ленивой инициализации в многопоточной среде, который минимизирует накладные расходы на синхронизацию.
Проблема: Без DCL при ленивой инициализации Singleton приходится синхронизировать каждый вызов getInstance():
// Медленная реализация
public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
Решение с DCL:
public class Singleton {
// КЛЮЧЕВОЕ: volatile для Java 5+
private static volatile Singleton instance;
private Singleton() {
// приватный конструктор
}
public static Singleton getInstance() {
// Первая проверка (без блокировки)
if (instance == null) {
synchronized (Singleton.class) {
// Вторая проверка (под блокировкой)
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
Почему нужен volatile (начиная с Java 5):
Без volatile возможна ситуация, когда один поток видит частично инициализированный объект из-за переупорядочивания инструкций:
// Без volatile может произойти:
// 1. Выделение памяти
// 2. Запись ссылки в instance (ещё до инициализации!)
// 3. Инициализация полей
// Другой поток на шаге 2 увидит instance != null и вернёт "сырой" объект
Альтернативы в современных Java:
-
Holder pattern (рекомендуется):
public class Singleton { private Singleton() {} private static class Holder { private static final Singleton INSTANCE = new Singleton(); } public static Singleton getInstance() { return Holder.INSTANCE; } } -
Enum Singleton (самый безопасный):
public enum Singleton { INSTANCE; public void doSomething() { // реализация } }
Когда использовать DCL:
- Когда ленивая инициализация критична для производительности
- В legacy-коде, где нельзя изменить архитектуру
- Когда Singleton содержит тяжёлые ресурсы, которые не всегда нужны
Когда НЕ использовать:
- В новых проектах (предпочитайте Holder или Enum)
- Если не понимаете тонкостей memory model Java
- Для простых объектов, где overhead синхронизации незначителен