Gymterview
middle

Как создать потокобезопасный Singleton?

Существует несколько способов создания потокобезопасного Singleton в Java, каждый со своими компромиссами:

1. Static field (Eager initialization)

Пример
public class Singleton {
    public static final Singleton INSTANCE = new Singleton();
}

Экземпляр создаётся при загрузке класса. Класс-загрузчик гарантирует потокобезопасность инициализации.

  • Плюс: простота, гарантированная потокобезопасность.
  • Минус: экземпляр создаётся, даже если он никогда не будет использован (eager).

2. Enum

Пример
public enum Singleton {
    INSTANCE;

    public void doSomething() { /* ... */ }
}
  • Плюс: самый лаконичный способ, защита от десериализации и рефлексии «из коробки» (JVM гарантирует уникальность enum-констант).
  • Минус: невозможно наследование; enum загружается eager; не все считают использование enum для Singleton идиоматичным.
  • Рекомендация Джошуа Блоха (Effective Java): «enum — лучший способ реализации Singleton».

3. Synchronized Accessor

Пример
public class Singleton {
    private static Singleton instance;

    public static synchronized Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
}
  • Плюс: ленивая инициализация.
  • Минус: каждый вызов getInstance() проходит через synchronized — избыточная блокировка после первого создания.

4. Double Checked Locking + volatile

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

    public static Singleton getInstance() {
        Singleton localInstance = instance;
        if (localInstance == null) {
            synchronized (Singleton.class) {
                localInstance = instance;
                if (localInstance == null) {
                    instance = localInstance = new Singleton();
                }
            }
        }
        return localInstance;
    }
}
  • Плюс: ленивая инициализация, блокировка только при первом создании.
  • Минус: сложность кода, необходимость volatile.

5. On Demand Holder Idiom (Initialization-on-demand holder)

Пример
public class Singleton {
    private static class Holder {
        static final Singleton INSTANCE = new Singleton();
    }

    public static Singleton getInstance() {
        return Holder.INSTANCE;
    }
}
  • Плюс: ленивая инициализация (вложенный класс загружается только при первом вызове getInstance()), потокобезопасность гарантирована JLS (спецификация загрузки классов), никакой синхронизации при доступе.
  • Минус: не защищён от рефлексии и десериализации (в отличие от enum).

Сравнительная таблица:

Способ Ленивость Защита от рефлексии Защита от десериализации Сложность
Static field Нет Нет Нет Минимальная
Enum Нет Да Да Минимальная
Synchronized Да Нет Нет Низкая
DCL + volatile Да Нет Нет Средняя
Holder Idiom Да Нет Нет Низкая

На собеседовании часто спрашивают: «Какой способ лучше?» Ответ зависит от контекста. Для большинства случаев — Enum (если не нужно наследование) или Holder Idiom (если нужна ленивость и привычный класс). DCL уместен, если нужно передать параметры в конструктор при первом вызове.