Що таке синглтон?
Сінглтон - це один із найпростіших шаблонів (патернів) проектування, який застосовується до класу. Іноді кажуть: "цей клас - синглтон", маючи на увазі, що цей клас реалізує патерн проектування синглтон. Іноді потрібно написати клас, у якого можна буде створити лише один об'єкт. Наприклад, клас, який відповідає за логування або підключення до бази даних. Шаблон проектування синглтон визначає, як ми можемо виконати таке завдання. Сінглтон - це шаблон (патерн) проектування, який робить дві речі:-
Дає гарантію, що у класу буде лише один екземпляр класу.
-
Надає глобальну точку доступу до екземпляра цього класу.
-
Приватний конструктор. Обмежує можливість створення об'єктів класу поза самим класом.
-
Публічний статичний метод, який повертає екземпляр класу. Цей метод називають
getInstance
. Це глобальна точка доступу до екземпляра класу.
Варіанти реалізації
Шаблон проектування синглтон застосовують по-різному. Кожен варіант по-своєму хороший і поганий. Тут як завжди: ідеалу немає, але треба до нього прагнути. Але насамперед давай визначимося, що таке добре і що таке погано, і які метрики впливають на оцінку реалізації шаблону проектування. Почнемо із позитивного. Ось критерії, які надають реалізації соковитості та привабливості:-
Лінива ініціалізація: коли клас завантажується під час роботи програми саме тоді, коли він потрібний.
-
Простота та прозорість коду: метрика, звичайно, суб'єктивна, але важлива.
-
Потокобезпека: коректна робота у багатопотоковому середовищі.
-
Висока продуктивність у багатопотоковому середовищі: потоки блокують один одного мінімально, або взагалі не блокують при спільному доступі до ресурсу.
-
Не лінива ініціалізація: коли клас завантажується при старті програми, незалежно від того, потрібен він чи ні (парадокс, у світі IT краще бути ледарем)
-
Складність та погана читаність коду. Метрика також суб'єктивна. Вважатимемо, що якщо кров пішла з очей, реалізація так собі.
-
Відсутність потокобезпеки. Іншими словами, "потоконебезпечність". Некоректна робота у багатопотоковому середовищі.
-
Низька продуктивність у многопоточной середовищі: потоки блокують одне одного постійно чи нерідко, при спільному доступі до ресурсу.
Код
Тепер ми готові розглянути різні варіанти реалізації з перерахуванням плюсів та мінусів:Simple Solution
public class Singleton {
private static final Singleton INSTANCE = new Singleton();
private Singleton() {
}
public static Singleton getInstance() {
return INSTANCE;
}
}
Найпростіша реалізація. Плюси:
-
Простота та прозорість коду
-
Потокобезпека
-
Висока продуктивність у багатопотоковому середовищі
- Чи не лінива ініціалізація.
Lazy Initialization
public class Singleton {
private static Singleton INSTANCE;
private Singleton() {}
public static Singleton getInstance() {
if (INSTANCE == null) {
INSTANCE = new Singleton();
}
return INSTANCE;
}
}
Плюси:
-
Лінива ініціалізація.
-
Чи не потокобезпечно
Synchronized Accessor
public class Singleton {
private static Singleton INSTANCE;
private Singleton() {
}
public static synchronized Singleton getInstance() {
if (INSTANCE == null) {
INSTANCE = new Singleton();
}
return INSTANCE;
}
}
Плюси:
-
Лінива ініціалізація.
-
Потокобезпека
-
Низька продуктивність у багатопотоковому середовищі
getInstance
синхронізований, і входити до нього можна лише по одному. Насправді нам потрібно синхронізувати не весь метод, а лише ту його частину, де ми ініціалізуємо новий об'єкт класу. Але ми не можемо просто обернути до synchronized
блоку частину, що відповідає за створення нового об'єкта: це не забезпечить потокобезпеку. Все трохи складніше. Правильний спосіб синхронізації представлений нижче:
Double Checked Locking
public class Singleton {
private static Singleton INSTANCE;
private Singleton() {
}
public static Singleton getInstance() {
if (INSTANCE == null) {
synchronized (Singleton.class) {
if (INSTANCE == null) {
INSTANCE = new Singleton();
}
}
}
return INSTANCE;
}
}
Плюси:
-
Лінива ініціалізація.
-
Потокобезпека
-
Висока продуктивність у багатопотоковому середовищі
-
Не підтримується на версіях Java нижче за 1.5 (у версії 1.5 виправабо роботу ключового слова volatile)
INSTANCE
має бути або final
, або volatile
. Остання реалізація, яку сьогодні обговоримо, — Class Holder Singleton
.
Class Holder Singleton
public class Singleton {
private Singleton() {
}
private static class SingletonHolder {
public static final Singleton HOLDER_INSTANCE = new Singleton();
}
public static Singleton getInstance() {
return SingletonHolder.HOLDER_INSTANCE;
}
}
Плюси:
-
Лінива ініціалізація.
-
Потокобезпека.
-
Висока продуктивність у багатопотоковому середовищі.
-
Для коректної роботи потрібна гарантія, що об'єкт класу
Singleton
ініціалізується без помилок. Інакше перший виклик методуgetInstance
закінчиться помилкоюExceptionInInitializerError
, проте наступніNoClassDefFoundError
.
Реалізація | Лінива ініціалізація | Потокобезпека | Швидкість роботи при багатопоточності | Коли використати? |
---|---|---|---|---|
Simple Solution | - | + | Швидко | Ніколи. Або коли не важлива лінива ініціалізація. Але краще ніколи. |
Lazy Initialization | + | - | Не застосовується | Завжди, коли не потрібна багатопоточність |
Synchronized Accessor | + | + | Повільно | Ніколи. Або коли швидкість роботи при багатопоточності не має значення. Але краще ніколи |
Double Checked Locking | + | + | Швидко | В окремих випадках, коли потрібно обробляти винятки при створенні синглтона. (коли не застосовується Class Holder Singleton) |
Class Holder Singleton | + | + | Швидко | Завжди коли потрібна багатопоточність і є гарантія, що об'єкт синглтон класу буде створений без проблем. |
Плюси та мінуси патерну Singleton
Загалом синглтон робить саме те, що від нього чекають:-
Дає гарантію, що у класу буде лише один екземпляр класу.
-
Надає глобальну точку доступу до екземпляра цього класу.
-
Сінглтон порушує SRP (Single Responsibility Principle) - клас синглтона, окрім безпосередніх обов'язків, займається ще й контролюванням кількості своїх екземплярів.
-
Залежність традиційного класу чи способу від синглтона не видно у громадському договорі класу.
-
Глобальні змінні це погано. Сінглтон перетворюється в результаті на одну здоровенну глобальну змінну.
-
Наявність синглтона знижує тестованість програми загалом і класів, які використовують синглтон, зокрема.
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ