1. Въведение
В тази кратка статия ще обсъдим двата най-популярни начина за внедряване на Singletons в обикновена Java.
2. Сингълтон, базиран на класа
Най-популярният подход е да се внедри Singleton, като се създаде редовен клас и се увери, че той има:
- Частен конструктор
- Статично поле, съдържащо единствения му екземпляр
- Статичен фабричен метод за получаване на екземпляра
Ще добавим и свойство за информация, само за по-късна употреба. И така, нашето изпълнение ще изглежда така:
public final class ClassSingleton { private static ClassSingleton INSTANCE; private String info = "Initial info class"; private ClassSingleton() { } public static ClassSingleton getInstance() { if(INSTANCE == null) { INSTANCE = new ClassSingleton(); } return INSTANCE; } // getters and setters }
Въпреки че това е често срещан подход, важно е да се отбележи, че може да бъде проблематично в сценарии с много нишки , което е основната причина за използването на Singletons.
Просто казано, това може да доведе до повече от един случай, нарушавайки основния принцип на шаблона. Въпреки че има заключващи решения на този проблем, следващият ни подход решава тези проблеми на основно ниво.
3. Enum Singleton
Продължавайки напред, нека не обсъждаме друг интересен подход - който е да използваме изброявания:
public enum EnumSingleton { INSTANCE("Initial class info"); private String info; private EnumSingleton(String info) { this.info = info; } public EnumSingleton getInstance() { return INSTANCE; } // getters and setters }
Този подход има сериализация и безопасност на нишките, гарантирани от самото изпълнение на enum, което гарантира вътрешно, че е наличен само единичният екземпляр, коригирайки проблемите, посочени в изпълнението на базата на класа.
4. Употреба
За да използваме нашия ClassSingleton , просто трябва да вземем екземпляра статично:
ClassSingleton classSingleton1 = ClassSingleton.getInstance(); System.out.println(classSingleton1.getInfo()); //Initial class info ClassSingleton classSingleton2 = ClassSingleton.getInstance(); classSingleton2.setInfo("New class info"); System.out.println(classSingleton1.getInfo()); //New class info System.out.println(classSingleton2.getInfo()); //New class info
Що се отнася до EnumSingleton , можем да го използваме като всеки друг Java Enum:
EnumSingleton enumSingleton1 = EnumSingleton.INSTANCE.getInstance(); System.out.println(enumSingleton1.getInfo()); //Initial enum info EnumSingleton enumSingleton2 = EnumSingleton.INSTANCE.getInstance(); enumSingleton2.setInfo("New enum info"); System.out.println(enumSingleton1.getInfo()); // New enum info System.out.println(enumSingleton2.getInfo()); // New enum info
5. Общи клопки
Сингълтънът е измамно прост модел на проектиране и има няколко често срещани грешки, които програмистът може да извърши, когато създава сингълтон.
Различаваме два вида проблеми с единични:
- екзистенциален (имаме ли нужда от единичен?)
- внедряване (прилагаме ли го правилно?)
5.1. Екзистенциални въпроси
Концептуално сингълтонът е вид глобална променлива. Като цяло знаем, че глобалните променливи трябва да се избягват - особено ако техните състояния са променливи.
Не казваме, че никога не трябва да използваме единични. Казваме обаче, че може да има по-ефективни начини за организиране на нашия код.
Ако изпълнението на метод зависи от единичен обект, защо да не го предадете като параметър? В този случай ние изрично показваме от какво зависи методът. В резултат на това можем лесно да се подиграем на тези зависимости (ако е необходимо), когато извършваме тестване.
Например единични тонове често се използват за обхващане на конфигурационните данни на приложението (т.е. връзка с хранилището). Ако се използват като глобални обекти, става трудно да се избере конфигурацията за тестовата среда.
Следователно, когато стартираме тестовете, производствената база данни се разваля с тестовите данни, което едва ли е приемливо.
Ако се нуждаем от сингълтон, бихме могли да обмислим възможността да делегираме неговата инстанция на друг клас - нещо като фабрика - който трябва да се погрижи да има само един екземпляр на сингълтона в игра.
5.2. Въпроси за изпълнение
Въпреки че единичните изглеждат доста прости, техните реализации могат да страдат от различни проблеми. Всички водят до факта, че в крайна сметка може да имаме повече от един екземпляр на класа.
Синхронизация
Внедряването с частен конструктор, което представихме по-горе, не е безопасно за нишки: работи добре в еднонишна среда, но в многонишкова трябва да използваме техниката на синхронизация, за да гарантираме атомността на операцията:
public synchronized static ClassSingleton getInstance() { if (INSTANCE == null) { INSTANCE = new ClassSingleton(); } return INSTANCE; }
Обърнете внимание на ключовата дума, синхронизирана в декларацията на метода. Тялото на метода има няколко операции (сравнение, създаване на екземпляр и връщане).
При липса на синхронизация има възможност две нишки да преплитат изпълнението си по такъв начин, че изразът INSTANCE == null да изчислява на true за двете нишки и в резултат да се създадат два екземпляра на ClassSingleton .
Синхронизацията може значително да повлияе на производителността. Ако този код се извиква често, трябва да го ускорим, като използваме различни техники като мързелива инициализация или двойно проверено заключване (имайте предвид, че това може да не работи според очакванията поради оптимизации на компилатора). Повече подробности можем да видим в нашия урок „Заключване с двойна проверка с еднократно“.
Няколко екземпляра
Има няколко други проблема със сингълтоните, свързани със самия JVM, които могат да доведат до многократни екземпляри на сингълтон. Тези въпроси са доста фини и ще дадем кратко описание за всеки от тях:
- Предполага се, че един сингъл е уникален за JVM. Това може да е проблем за разпределени системи или системи, чиито вътрешни елементи са базирани на разпределени технологии.
- Всеки зареждащ клас може да зареди своята версия на синглона.
- Сингълтон може да бъде събран боклук, след като никой не държи препратка към него. Този проблем не води до наличието на множество единични екземпляри наведнъж, но когато се пресъздаде, екземплярът може да се различава от предишната си версия.
6. Заключение
В този бърз урок се фокусирахме върху това как да приложим шаблона на Singleton, използвайки само основната Java, и как да се уверим, че е последователен и как да използваме тези внедрения.
Пълното изпълнение на тези примери може да бъде намерено в GitHub.