← Назад к вопросам

Реализация паттерна Singleton

1.3 Junior🔥 111 комментариев
#Другое#Основы Java

Условие

Реализуйте паттерн проектирования Singleton на Java.

Singleton гарантирует, что класс имеет только один экземпляр, и предоставляет глобальную точку доступа к нему.

Требования

  • Приватный конструктор
  • Статический метод getInstance()
  • Ленивая инициализация (lazy initialization)
  • Потокобезопасность (thread-safe)

Варианты реализации

  1. Eager initialization (энергичная)
  2. Lazy initialization (ленивая)
  3. Double-checked locking
  4. Bill Pugh Singleton (с внутренним классом)
  5. Enum Singleton

Реализуйте минимум два варианта и объясните их плюсы и минусы.

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI23 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Реализация паттерна Singleton

Singleton — это один из самых известных и часто обсуждаемых паттернов проектирования. Он гарантирует, что класс имеет только один экземпляр в течение всего жизненного цикла приложения. Рассмотрим 5 различных способов реализации, от простейшего к самому продвинутому.

1. Eager Initialization (энергичная инициализация)

Экземпляр создаётся при загрузке класса:

public class SingletonEager {
    // Экземпляр создаётся сразу при загрузке класса
    private static final SingletonEager instance = new SingletonEager();
    
    // Приватный конструктор
    private SingletonEager() {
    }
    
    // Статический метод доступа
    public static SingletonEager getInstance() {
        return instance;
    }
}

Плюсы:

  • Простая реализация
  • Потокобезопасна по умолчанию (инициализация класса потокобезопасна в Java)
  • Нет проблем с отражением (Reflection)

Минусы:

  • Экземпляр создаётся даже если он никогда не будет использован
  • Нет ленивой инициализации

2. Lazy Initialization (ленивая инициализация) — НЕБЕЗОПАСНА

public class SingletonLazyUnsafe {
    private static SingletonLazyUnsafe instance;
    
    private SingletonLazyUnsafe() {
    }
    
    public static SingletonLazyUnsafe getInstance() {
        if (instance == null) {
            instance = new SingletonLazyUnsafe();
        }
        return instance;
    }
}

Проблема: При многопоточном доступе возможно создание двух экземпляров!

// Сценарий проблемы:
// Поток 1: проверил (instance == null) → true → начал создание
// Поток 2: проверил (instance == null) → true → начал создание
// Результат: 2 экземпляра!

Вывод: Никогда не используйте это в многопоточных приложениях!

3. Synchronized (синхронизированный) — МЕДЛЕННЫЙ

public class SingletonSynchronized {
    private static SingletonSynchronized instance;
    
    private SingletonSynchronized() {
    }
    
    public static synchronized SingletonSynchronized getInstance() {
        if (instance == null) {
            instance = new SingletonSynchronized();
        }
        return instance;
    }
}

Плюсы:

  • Потокобезопасен
  • Ленивая инициализация

Минусы:

  • Каждый вызов getInstance() требует синхронизации
  • Большое снижение производительности в многопоточной среде

4. Double-Checked Locking (оптимальный наивный подход)

public class SingletonDoubleChecked {
    private static volatile SingletonDoubleChecked instance;
    
    private SingletonDoubleChecked() {
    }
    
    public static SingletonDoubleChecked getInstance() {
        if (instance == null) {                    // Первая проверка
            synchronized (SingletonDoubleChecked.class) {
                if (instance == null) {            // Вторая проверка
                    instance = new SingletonDoubleChecked();
                }
            }
        }
        return instance;
    }
}

Важно: volatile ключевое слово!

volatile гарантирует, что видимость изменений будет обеспечена всем потокам. Без него возможны проблемы с видимостью.

Плюсы:

  • Потокобезопасен
  • Ленивая инициализация
  • Синхронизация только при первом создании
  • Хорошая производительность после инициализации

Минусы:

  • Сложнее для понимания
  • Требует знания о volatile и синхронизации

5. Bill Pugh Singleton (лучший способ) ⭐

public class SingletonBillPugh {
    // Вспомогательный статический класс
    private static class SingletonHelper {
        private static final SingletonBillPugh INSTANCE = new SingletonBillPugh();
    }
    
    private SingletonBillPugh() {
    }
    
    public static SingletonBillPugh getInstance() {
        return SingletonHelper.INSTANCE;
    }
}

Как это работает:

  • Внутренний класс SingletonHelper загружается только при первом вызове getInstance()
  • Инициализация класса в Java потокобезопасна
  • Никакой синхронизации не требуется

Плюсы:

  • Потокобезопасен (без явной синхронизации)
  • Ленивая инициализация
  • Читаемый код
  • Оптимальная производительность
  • Не уязвим к Reflection

Минусы: Практически их нет!

6. Enum Singleton (абсолютно лучший способ) ⭐⭐

public enum SingletonEnum {
    INSTANCE;
    
    private String data;
    
    // Методы синглтона
    public void doSomething() {
        System.out.println("Doing something");
    }
    
    public String getData() {
        return data;
    }
    
    public void setData(String data) {
        this.data = data;
    }
}

// Использование:
public class Main {
    public static void main(String[] args) {
        SingletonEnum singleton = SingletonEnum.INSTANCE;
        singleton.doSomething();
    }
}

Плюсы:

  • Максимально простой синтаксис
  • Потокобезопасен автоматически
  • Защищён от Reflection и Serialization атак
  • Поддерживает Serialization из коробки
  • Читаемый и современный подход

Минусы:

  • Может быть необычно для старого кода
  • Сложнее расширить (нет наследования для enum)

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

СпособПотокобезопасностьЛенивостьПроизводительностьСложность
EagerОтличнаяНизкая
Lazy (unsafe)-Низкая
SynchronizedПлохаяНизкая
Double-CheckedХорошаяСредняя
Bill PughОтличнаяСредняя
EnumОтличнаяНизкая

Проблема с Reflection

Опасность для некоторых реализаций:

try {
    Constructor<SingletonEager> constructor = 
        SingletonEager.class.getDeclaredConstructor();
    constructor.setAccessible(true);
    SingletonEager newInstance = constructor.newInstance();
    // Теперь у нас 2 экземпляра!
} catch (Exception e) {
    e.printStackTrace();
}

Защита: Enum и некоторые реализации автоматически защищены от этого.

Рекомендация

Для современного Java (8+):

  • Используйте Enum Singleton для максимальной безопасности
  • Если нужна наследуемость → Bill Pugh Singleton
  • В legacy коде → Double-Checked Locking

Избегайте:

  • Ленивой инициализации без синхронизации
  • Synchronized методов из-за производительности