Одиночка (Singleton)

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

Ключевое уточнение для Java: «один» обычно означает один экземпляр на конкретный ClassLoader. В нескольких JVM, application contexts или class loaders экземпляров может быть несколько.

Когда встречается

  • stateless registry;
  • локальный cache с одним владельцем;
  • holder для неизменяемой конфигурации;
  • объект, жизненным циклом которого управляет DI container.

Соединение с БД — плохой пример одиночного ресурса: приложению обычно нужен pool из нескольких connections.

Eager initialization

public final class MetricsRegistry {
    private static final MetricsRegistry INSTANCE =
            new MetricsRegistry();
 
    private MetricsRegistry() {
    }
 
    public static MetricsRegistry instance() {
        return INSTANCE;
    }
}

Инициализация класса в JVM потокобезопасна. Экземпляр будет создан при инициализации MetricsRegistry, даже если позже не понадобится.

Initialization-on-demand holder

public final class MetricsRegistry {
 
    private MetricsRegistry() {
    }
 
    private static class Holder {
        private static final MetricsRegistry INSTANCE =
                new MetricsRegistry();
    }
 
    public static MetricsRegistry instance() {
        return Holder.INSTANCE;
    }
}

Вложенный Holder инициализируется при первом обращении к его полю. Получается lazy initialization без ручного synchronized.

Enum Singleton

public enum ApplicationClock {
    INSTANCE;
 
    public Instant now() {
        return Instant.now();
    }
}

Enum защищён правилами языка от обычного создания через reflection и корректно восстанавливает singleton-семантику при Java serialization. Но глобальная точка доступа всё равно связывает клиентов с конкретной реализацией.

Почему простой lazy-вариант сломан

if (instance == null) {
    instance = new MetricsRegistry();
}
return instance;

Два потока могут одновременно увидеть null и создать два объекта. Double-checked locking работает только при volatile:

private static volatile MetricsRegistry instance;
 
public static MetricsRegistry instance() {
    MetricsRegistry result = instance;
    if (result == null) {
        synchronized (MetricsRegistry.class) {
            result = instance;
            if (result == null) {
                result = new MetricsRegistry();
                instance = result;
            }
        }
    }
    return result;
}

Для обычного приложения holder или enum проще и надёжнее.

Почему Singleton считают проблемным

Само единственное владение ресурсом не является ошибкой. Проблемы создаёт скрытый глобальный доступ:

  • зависимость не видна в constructor signature;
  • состояние разделяют несвязанные тесты;
  • реализацию трудно заменить;
  • lifecycle живёт внутри класса вместе с прикладной ответственностью;
  • конкурентный доступ к mutable state остаётся отдельной задачей.

DI container позволяет иметь один экземпляр, но передавать его явно как зависимость:

final class ReportService {
    private final Clock clock;
 
    ReportService(Clock clock) {
        this.clock = clock;
    }
}

Это упрощает тестирование и не привязывает код к Clock.instance().

Spring singleton scope

Singleton bean в Spring — один bean instance на ApplicationContext и bean name. Это не гарантия одного Java-объекта на всю JVM. Два contexts могут создать по экземпляру.

Вопросы для собеседования

Гарантирует ли private constructor единственный объект?

Нет. Он только ограничивает обычный вызов конструктора. Нужны правила создания; остаются reflection, serialization, cloning и разные class loaders.

Делает ли Singleton объект потокобезопасным?

Нет. Потокобезопасно можно получить ссылку, но mutable поля объекта требуют собственной синхронизации.

Чем singleton bean отличается от GoF Singleton?

Container управляет scope и внедряет зависимость снаружи. Класс необязательно содержит static instance и глобальный accessor.

Когда Singleton оправдан?

Когда область единственности и lifecycle действительно определены, состояние контролируется, а клиенты не получают скрытую зависимость без необходимости.

Связанные заметки