Опановуйте патерн Singleton у Java за допомогою нашого посібника про потокобезпечність, тестування й сучасні альтернативи, такі як впровадження залежностей, для підтримуваного та масштабованого коду.
January 28, 2026 (2mo ago)
Посібник зі шаблону Singleton у Java для чистого коду
Опановуйте патерн Singleton у Java за допомогою нашого посібника про потокобезпечність, тестування й сучасні альтернативи, такі як впровадження залежностей, для підтримуваного та масштабованого коду.
← Back to blog
Посібник зі шаблону Singleton у Java для чистого коду
Резюме: Опановуйте патерн Singleton у Java за допомогою нашого посібника про потокобезпечність, тестування й сучасні альтернативи, такі як впровадження залежностей, для підтримуваного та масштабованого коду.
Вступ
Патерн Singleton у Java гарантує, що клас має лише один екземпляр і надає єдину, глобальну точку доступу до нього. Це корисно для об’єктів, таких як менеджери конфігурацій, пули з'єднань або центральні сервіси логування, де множинні екземпляри призвели б до непослідовного стану або марнотратного використання ресурсів. Розуміння того, як реалізувати безпечний та тестований Singleton — і коли його варто уникати — є ключовим для чистого, підтримуваного Java-коду.
Розуміння патерну Singleton

Корисна аналогія — вишка керування повітряним рухом в аеропорту. Ви не будуєте окрему вишку для кожного літака; кожен літак спілкується з однією й тією ж вишкою. Вишка — це єдине джерело істини. Саме таку роль відіграє Singleton в застосунку.
Патерн був популяризований у класичній літературі про шаблони проєктування і в корпоративних Java-системах1. Його основні обов'язки прості:
- Гарантувати один екземпляр — зазвичай шляхом роблення конструктора приватним.
- Надавати глобальну точку доступу — зазвичай це статичний метод, наприклад getInstance().
Ключові характеристики
- Лише один екземпляр: Клас запобігає створенню більше ніж одного екземпляра.
- Приватний конструктор: Запобігає прямій інстанціації з інших класів.
- Глобальна точка доступу: Статичний метод повертає єдиний екземпляр.
- Самостійний життєвий цикл: Клас керує власним екземпляром.
Висновок: Singleton забезпечує один об’єкт і контрольований глобальний доступ, щоб різні частини застосунку взаємодіяли з тим самим екземпляром.
Реалізація потокобезпечних Singleton у Java

Наївний лениво-ініціалізований Singleton простий, але не потокобезпечний. Розгляньмо цей базовий приклад:
public class BasicLazySingleton {
private static BasicLazySingleton instance;
private BasicLazySingleton() {}
public static BasicLazySingleton getInstance() {
if (instance == null) {
instance = new BasicLazySingleton();
}
return instance;
}
}
У багатопотоковому середовищі два потоки можуть помітити, що instance == null, і обидва створять новий екземпляр. Пряме виправлення — синхронізувати getInstance(), але це викликає непотрібне блокування при кожному виклику.
Синхронізований метод (працює, але дорого)
public class SynchronizedSingleton {
private static SynchronizedSingleton instance;
private SynchronizedSingleton() {}
public static synchronized SynchronizedSingleton getInstance() {
if (instance == null) {
instance = new SynchronizedSingleton();
}
return instance;
}
}
Це вирішує проблему потокобезпеки, але шкодить продуктивності, бо синхронізація виконується при кожному доступі.
Bill Pugh (ініціалізація за запитом через Holder)
Чистіший підхід — ідіома Initialization-on-demand Holder. Вона дає ледачу ініціалізацію та потокобезпечність без накладних витрат синхронізації, бо ініціалізація класів у JVM є потокобезпечною2.
public class BillPughSingleton {
private BillPughSingleton() {}
private static class SingletonHolder {
private static final BillPughSingleton INSTANCE = new BillPughSingleton();
}
public static BillPughSingleton getInstance() {
return SingletonHolder.INSTANCE;
}
}
Це покладається на JVM, щоб завантажити SingletonHolder лише коли викликається getInstance(), а гарантії ініціалізації класу забезпечують потокобезпеку.
Enum Singleton (рекомендовано)
Joshua Bloch радить використовувати одноелементний enum для Singleton. Це лаконічно і захищає від атак через рефлексію та серіалізацію3.
public enum EnumSingleton {
INSTANCE;
public void someMethod() {
// business logic
}
}
Переваги:
- Мінімум коду
- Потокобезпека, надана JVM
- Захист при серіалізації
- Міцний захист від створення екземплярів через рефлексію
У більшості випадків enum Singleton — найнадійніший і найпідтримуваніший вибір.
Приховані витрати патерну Singleton

Хоча Singleton вирішує певну задачу, він вводить приховані витрати: щільне зв’язування, глобальний стан та знижену тестованість.
Коли код викликає Singleton.getInstance(), ця залежність прихована. Публічний контракт класу не показує, що він покладається на глобальний об’єкт. Це призводить до:
- Жорсткого коду, який важко змінювати
- Тестів, що крихкі або вимагають реального глобального екземпляра
- Ускладнення запуску тестів паралельно через спільний стан
Проблеми з тестуванням
Singleton ускладнює ізольоване модульне тестування. Ви не можете легко підмінити мок, тому тести часто використовують реальну реалізацію. Це може спричиняти повільні тести, випадкову залежність від зовнішніх систем і крихкі CI-конвеєри.
Клас, що залежить від Singleton, приховує цю залежність із підпису методу, що ускладнює розуміння та підтримку коду.
Глобальний стан і приховані залежності
Singleton по суті є глобальною змінною. Глобальний стан затемнює потоки інформації і створює взаємозалежності, які важко розплутати. Це ускладнює відлагодження і сповільнює розробку.
Для детальнішої інформації про поширені антишаблони та підтримуваність див. наш посібник із шаблонів проєктування в ООП та стратегій тестування.
Сучасні альтернативи Singleton
У міру розвитку систем розробники почали використовувати підходи, які уникають недоліків Singleton, зберігаючи контрольоване створення об'єктів.
Впровадження залежностей
Dependency Injection (DI) змінює відповідальність: клієнти явно оголошують свої залежності, а зовнішній контейнер надає їх. Це робить залежності явними і легкими для заміни в тестах. Фреймворки DI, такі як Spring і Guice, керують життєвими циклами об’єктів і їх зв’язуванням за вас4.
Переваги DI:
- Розв’язування залежностей — компоненти залежать від абстракцій, а не від конкретних класів
- Тестованість — можна інжектити моки або фейки
- Гнучкість — заміна реалізацій через конфігурацію
Це відповідає принципам інверсії керування і дає більш зрозумілі, підтримувані системи7.
Фабрики
Патерн Factory централізує логіку створення. Фабрика може повертати той самий екземпляр або створювати нові екземпляри за потреби. Код клієнта запитує у фабрики об’єкт, не знаючи, як він створюється, що робить код модульним і тестованим.
Обмежені за сферою екземпляри
Іноді потрібен один екземпляр, але лише в обмеженій сфері (запит, сесія або застосунок). Фреймворки підтримують бінзи з областю request або session, щоб забезпечити баланс між повторним використанням ресурсів і ізоляцією.
Singletons у розподілених системах
Локальний до JVM Singleton не дає одного екземпляра між кількома інстансами сервісу. Мікросервіси запускають кілька JVM, отже потрібні розподілені рішення для спільного стану, наприклад Redis або централізований сервіс конфігурації, як-от Consul чи Spring Cloud Config6.
Як рефакторити Singletons у спадковому коді

Рефакторинг Singleton вимагає обережності. Основна проблема — пряма залежність від статичного виклику getInstance(). Поступовий, методичний підхід знижує ризик.
Покрокова стратегія:
- Впровадьте інтерфейс, який описує публічні методи Singleton, і зробіть так, щоб Singleton його реалізовував.
- Зробіть залежності явними, додаючи параметри конструктора в класи, що використовують Singleton.
- Використовуйте фабрику або DI-контейнер (Spring, Guice), щоб надавати реалізацію як керований екземпляр.
- Замініть виклики Singleton.getInstance() на залежності, що інжектяться через конструктор.
- Видаліть код, специфічний для Singleton, як тільки всі виклики отримуватимуть залежність зовнішньо.
Переваги: покращена модульність, тестованість і прозорість. Рефакторинг Singleton перетворює жорстку кодову базу на гнучкі, тестовані компоненти.
Поширені запитання
Чи є патерн Singleton антишаблоном?
Часто — так. Singleton вводить глобальний стан і щільне зв’язування, що знижує тестованість і підвищує витрати на довгострокове утримання. Використовуйте його помірковано і віддавайте перевагу DI або обмеженим за сферою екземплярам, де це можливо.
Як можна зламати Singleton у Java?
Рефлексія та серіалізація можуть створити нові екземпляри, якщо ви явно проти цього не захищені. Використання enum для Singleton уникає цих проблем3.
Чи підходить Singleton для мікросервісів?
Ні. Singleton має область видимості в межах JVM, тому кожен інстанс сервісу матиме свій власний Singleton. Для спільного стану між сервісами використовуйте розподілені системи, як-от Redis або централізовані сервіси конфігурації6.
Три стислих підсумки запитань і відповідей
П: Коли мені слід використовувати Singleton? В: Тільки коли один екземпляр дійсно уособлює єдиний глобальний ресурс всередині однієї JVM і немає кращої альтернативи. Якщо мусите — віддавайте перевагу enum Singleton.
П: Як зробити потокобезпечний, ледачий Singleton? В: Використовуйте ідіому Initialization-on-demand Holder (Bill Pugh) або enum. Обидва забезпечують потокобезпеку з мінімальними накладними витратами; enum також захищає від проблем із серіалізацією та рефлексією23.
П: Що використовувати замість Singleton для кращої тестованості? В: Використовуйте впровадження залежностей, фабрики або екземпляри з обмеженою сферою, щоб залежності були явними і легко замінювалися в тестах47.
ШІ пише код.Ви робите його довговічним.
В епоху прискорення ШІ чистий код — це не просто хороша практика — це різниця між системами, які масштабуються, та кодовими базами, які руйнуються під власною вагою.