← Назад к вопросам
Что такое Double-Checked Locking?
3.0 Senior🔥 151 комментариев
#SOLID и паттерны проектирования#Многопоточность
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI23 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Double-Checked Locking: оптимизация синхронизации
Double-Checked Locking (DCL) — это паттерн синхронизации, который используется для снижения накладных расходов на получение блокировки в многопоточной среде. Основная идея: сначала проверяем без блокировки, потом проверяем ещё раз под блокировкой.
Классический пример: Lazy Initialization
// Наивный способ - синхронизируем ВСЕ операции
public class Singleton {
private static Singleton instance;
public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
Проблема: После инициализации instance все последующие вызовы getInstance() входят в синхронизированный метод, что дорого.
Double-Checked Locking решение
public class Singleton {
private static volatile Singleton instance; // ← ВАЖНО: volatile!
public static Singleton getInstance() {
// Первая проверка БЕЗ блокировки (быстро)
if (instance == null) {
synchronized (Singleton.class) {
// Вторая проверка ПОД блокировкой (безопасно)
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
Как это работает:
- Первая проверка (без блокировки): если instance != null → возвращаем
- Вторая проверка (с блокировкой): ещё раз проверяем, может другой поток создал
- Инициализация только один раз
Почему нужен volatile?
// ❌ БЕЗ volatile - МОЖЕТ НЕ РАБОТАТЬ!
private static Singleton instance; // Опасно!
// ✓ С volatile - ПРАВИЛЬНО
private static volatile Singleton instance; // Безопасно
Причина: volatile гарантирует visibility между потоками:
- Изменения видны сразу всем потокам
- Предотвращает кэширование значения в регистрах CPU
- Работает с Java Memory Model
public class VolatileExample {
public static void main(String[] args) throws InterruptedException {
Singleton s1 = Singleton.getInstance();
Singleton s2 = Singleton.getInstance();
System.out.println(s1 == s2); // true (один и тот же объект)
// Поток 1 создал объект, Поток 2 увидел его благодаря volatile
}
}
Детальное объяснение
public class DCLDemo {
private static volatile DCLDemo instance;
private String data;
private DCLDemo() {
// Дорогая инициализация
this.data = initializeData();
}
public static DCLDemo getInstance() {
// БЫСТРЫЙ ПУТЬ (99% времени)
if (instance == null) { // ← Читаем без блокировки
// Только сюда входим при первом вызове
synchronized (DCLDemo.class) {
// БЕЗОПАСНЫЙ ПУТЬ
if (instance == null) { // ← Проверка под блокировкой
instance = new DCLDemo(); // ← Создаём ровно один раз
}
}
}
return instance;
}
private String initializeData() {
// Дорогие операции
return "initialized";
}
}
Многопоточный сценарий
public class ThreadScenario {
public static void main(String[] args) throws InterruptedException {
// Запускаем несколько потоков
Runnable task = () -> {
DCLDemo instance = DCLDemo.getInstance();
System.out.println(Thread.currentThread().getName() + ": " + instance);
};
// Первый поток
Thread t1 = new Thread(task, "Thread-1");
// Второй поток (может придти пока t1 создаёт объект)
Thread t2 = new Thread(task, "Thread-2");
// Третий поток
Thread t3 = new Thread(task, "Thread-3");
t1.start();
t2.start();
t3.start();
t1.join();
t2.join();
t3.join();
// Все потоки получат ОДН И ТОТ ЖЕ объект!
}
}
Производительность: сравнение
public class PerformanceComparison {
// Вариант 1: Synchronized метод (медленно после инициализации)
public static class SynchronizedSingleton {
private static SynchronizedSingleton instance;
public static synchronized SynchronizedSingleton getInstance() {
if (instance == null) {
instance = new SynchronizedSingleton();
}
return instance;
}
}
// Вариант 2: Double-Checked Locking (быстро после инициализации)
public static class DCLSingleton {
private static volatile DCLSingleton instance;
public static DCLSingleton getInstance() {
if (instance == null) {
synchronized (DCLSingleton.class) {
if (instance == null) {
instance = new DCLSingleton();
}
}
}
return instance;
}
}
// Вариант 3: Class Loader (самый быстрый, рекомендуется)
public static class ClassLoaderSingleton {
private static class Holder {
static final ClassLoaderSingleton INSTANCE = new ClassLoaderSingleton();
}
public static ClassLoaderSingleton getInstance() {
return Holder.INSTANCE; // ← Class Loader гарантирует thread safety
}
}
}
Когда использовать DCL
Используй DCL:
- Lazy initialization в многопоточном контексте
- Когда инициализация дорогая
- Когда нужна быстрая работа после инициализации
// Пример: инициализация конфигурации
public class ConfigManager {
private static volatile ConfigManager instance;
private Map<String, String> config;
public static ConfigManager getInstance() {
if (instance == null) {
synchronized (ConfigManager.class) {
if (instance == null) {
instance = new ConfigManager();
instance.loadConfiguration(); // Дорогая операция I/O
}
}
}
return instance;
}
private void loadConfiguration() {
// Читаем конфиг из файла
config = loadFromFile("config.properties");
}
}
Но ЛУЧШЕ используй:
- Class Loader паттерн (Bill Pugh Singleton)
- Enum Singleton (самый безопасный)
- Spring @Bean (для managed beans)
// ✓ ЛУЧШИЙ СПОСОБ - Enum
public enum Singleton {
INSTANCE;
public void doSomething() {
// ...
}
}
// Использование
Singleton.INSTANCE.doSomething();
Потенциальные проблемы
// ❌ БЕЗ volatile могут быть проблемы
private static Singleton instance; // Может не работать корректно
// ✓ volatile обязателен
private static volatile Singleton instance; // Правильно
// ❌ Только одна проверка - race condition
if (instance == null) {
instance = new Singleton(); // Могут создать два объекта!
}
// ✓ Двойная проверка - безопасно
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton(); // Ровно один объект
}
}
}
Double-Checked Locking — это важный паттерн для оптимизации многопоточных приложений, но его нужно использовать осторожно, убедившись, что volatile добавлен правильно.