Зачем нужен синглтон в Java
Что проверяют
Не столько умение написать класс с приватным конструктором, сколько понимание, когда синглтон оправдан и чем он вредит. Следом почти всегда просят сделать его потокобезопасным и спрашивают, как такой код тестировать.
Как отвечать
Синглтон гарантирует один экземпляр класса и даёт к нему глобальную точку доступа. Он оправдан там, где второй экземпляр означает ошибку или лишнюю дорогую инициализацию: реестр, пул соединений, загруженная конфигурация, кеш. Логгер часто приводят в пример, но в SLF4J это обычный вызов LoggerFactory.getLogger, а не ваш синглтон.
Реализации, которые стоит знать:
- enum:
enum Registry { INSTANCE; }. Потокобезопасен, защищён от повторного создания через рефлексию и сериализацию. Именно его советует Блох в Effective Java; - Holder: статический вложенный класс с полем экземпляра. Ленивый и потокобезопасный за счёт гарантий инициализации классов, без явной синхронизации;
- double-checked locking: работает только с
volatileу поля экземпляра, иначе другой поток может увидеть не до конца созданный объект; - synchronized-метод: корректен, но захватывает монитор на каждом вызове. Проще и лучше взять Holder.
Дальше честно назовите минусы: глобальное состояние, скрытые зависимости, трудные тесты (статический вызов не подменить без Mockito.mockStatic), состояние, которое протекает между тестами. Поэтому в прикладном коде паттерн почти вытеснен контейнером: в Spring бин по умолчанию singleton, но единственность обеспечивает контекст, а класс остаётся обычным и получает зависимости через конструктор.
Где спотыкаются
Пишут double-checked locking без volatile. Это классическая ошибка модели памяти, и её спрашивают специально.
Считают, что синглтон один на JVM. Он один на загрузчик классов, и в сервере приложений или системе плагинов экземпляров может оказаться несколько.
Путают паттерн и singleton scope в Spring. Бин один на контекст, а класс при этом обычный, и в юнит-тесте его спокойно создают через new. У паттерна приватный конструктор запрещает именно это.