Який ефективний спосіб реалізувати однотонну схему в Java?
Який ефективний спосіб реалізувати однотонну схему в Java?
Відповіді:
Використовуйте перелік:
public enum Foo {
INSTANCE;
}
Джошуа Блох пояснив такий підхід у своїй Ефективній розмові на Java Reloaded на Google I / O 2008: посилання на відео . Також дивіться слайди 30-32 його презентації ( ефективні_java_reloaded.pdf ):
Правильний шлях реалізації серіалізаційного синглтона
public enum Elvis { INSTANCE; private final String[] favoriteSongs = { "Hound Dog", "Heartbreak Hotel" }; public void printFavorites() { System.out.println(Arrays.toString(favoriteSongs)); } }
Edit: онлайн частина «Ефективне Java» , говорить:
"Цей підхід функціонально еквівалентний підходу на публічному полю, за винятком того, що він є більш стислим, забезпечує механізм серіалізації безкоштовно і забезпечує гарантію залізобезпечного захисту від багаторазових інстанцій, навіть за умови складних серіалізаційних або рефлексійних атак. Хоча цей підхід має ще не отримавши широкого поширення, одноелементний тип enum - найкращий спосіб реалізувати одинарний ".
Залежно від використання існує кілька «правильних» відповідей.
Оскільки java5 найкращий спосіб зробити це - використовувати enum:
public enum Foo {
INSTANCE;
}
Перед java5, найпростіший випадок:
public final class Foo {
private static final Foo INSTANCE = new Foo();
private Foo() {
if (INSTANCE != null) {
throw new IllegalStateException("Already instantiated");
}
}
public static Foo getInstance() {
return INSTANCE;
}
public Object clone() throws CloneNotSupportedException{
throw new CloneNotSupportedException("Cannot clone instance of this class");
}
}
Перейдемо до коду. По-перше, ви хочете, щоб клас був остаточним. У цьому випадку я використав final
ключове слово, щоб повідомити користувачам, що воно остаточне. Тоді вам потрібно зробити конструктор приватним, щоб перешкодити користувачам створювати свій власний Foo. Викидання виключення з конструктора заважає користувачам використовувати відображення для створення другого Foo. Потім ви створюєте private static final Foo
поле для утримування єдиного екземпляра та public static Foo getInstance()
спосіб його повернення. Специфікація Java гарантує, що конструктор викликається лише при першому використанні класу.
Якщо у вас дуже великий об'єкт або важкий будівельний код І також є інші доступні статичні методи або поля, які можуть бути використані перед необхідним екземпляром, тоді і лише тоді вам потрібно використовувати ініціальну ліниву.
Ви можете використовувати a private static class
для завантаження екземпляра. Код виглядатиме так:
public final class Foo {
private static class FooLoader {
private static final Foo INSTANCE = new Foo();
}
private Foo() {
if (FooLoader.INSTANCE != null) {
throw new IllegalStateException("Already instantiated");
}
}
public static Foo getInstance() {
return FooLoader.INSTANCE;
}
}
Оскільки рядок private static final Foo INSTANCE = new Foo();
виконується лише тоді, коли насправді використовується клас FooLoader, це піклується про ледачий екземпляр, і чи гарантовано він захищений потоком.
Якщо ви також хочете мати можливість серіалізувати ваш об'єкт, вам потрібно переконатися, що десеріалізація не створить копію.
public final class Foo implements Serializable {
private static final long serialVersionUID = 1L;
private static class FooLoader {
private static final Foo INSTANCE = new Foo();
}
private Foo() {
if (FooLoader.INSTANCE != null) {
throw new IllegalStateException("Already instantiated");
}
}
public static Foo getInstance() {
return FooLoader.INSTANCE;
}
@SuppressWarnings("unused")
private Foo readResolve() {
return FooLoader.INSTANCE;
}
}
Метод readResolve()
забезпечить повернення єдиного екземпляра, навіть коли об'єкт був серіалізований у попередньому запуску вашої програми.
Відмова: Я щойно узагальнив усі приголомшливі відповіді і написав це своїми словами.
Під час реалізації Singleton у нас є 2 варіанти
1. Ледача завантаження
2. Рання завантаження
Ледаче завантаження додає біт накладних витрат (багато, якщо чесно), тому використовуйте його лише тоді, коли у вас є дуже великий об'єкт або важкий будівельний код. А також є інші доступні статичні методи або поля, які можуть бути використані до того, як потрібен екземпляр, тоді і лише тоді вам потрібно використовувати ліниву ініціалізацію. Інакше вибір раннього завантаження - хороший вибір.
Найбільш простим способом реалізації Singleton є
public class Foo {
// It will be our sole hero
private static final Foo INSTANCE = new Foo();
private Foo() {
if (INSTANCE != null) {
// SHOUT
throw new IllegalStateException("Already instantiated");
}
}
public static Foo getInstance() {
return INSTANCE;
}
}
Все добре, крім раннього завантаження сингла. Давайте спробуємо ліниво завантажений синглтон
class Foo {
// Our now_null_but_going_to_be sole hero
private static Foo INSTANCE = null;
private Foo() {
if (INSTANCE != null) {
// SHOUT
throw new IllegalStateException("Already instantiated");
}
}
public static Foo getInstance() {
// Creating only when required.
if (INSTANCE == null) {
INSTANCE = new Foo();
}
return INSTANCE;
}
}
Поки добрий, але наш герой не виживе, бореться наодинці з кількома злими нитками, які хочуть багато-багато примірників нашого героя. Тож давайте захистимо його від злих багаторізкових різьб
class Foo {
private static Foo INSTANCE = null;
// TODO Add private shouting constructor
public static Foo getInstance() {
// No more tension of threads
synchronized (Foo.class) {
if (INSTANCE == null) {
INSTANCE = new Foo();
}
}
return INSTANCE;
}
}
але цього недостатньо для захисту героя, Дійсно !!! Це найкраще, що ми можемо / повинні зробити, щоб допомогти нашому герою
class Foo {
// Pay attention to volatile
private static volatile Foo INSTANCE = null;
// TODO Add private shouting constructor
public static Foo getInstance() {
if (INSTANCE == null) { // Check 1
synchronized (Foo.class) {
if (INSTANCE == null) { // Check 2
INSTANCE = new Foo();
}
}
}
return INSTANCE;
}
}
Це називається "Ідентифікація подвійної перевірки блокування". Забути мінливе твердження легко і важко зрозуміти, чому це необхідно.
Детальніше: http://www.cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.html
Тепер ми впевнені в злій нитці, а як же жорстока серіалізація? Ми повинні переконатися, що навіть під час десеріалізації не створюється новий об'єкт
class Foo implements Serializable {
private static final long serialVersionUID = 1L;
private static volatile Foo INSTANCE = null;
// Rest of the things are same as above
// No more fear of serialization
@SuppressWarnings("unused")
private Object readResolve() {
return INSTANCE;
}
}
Метод readResolve()
забезпечить повернення єдиного екземпляра, навіть коли об'єкт був серіалізований у попередньому запуску нашої програми.
Нарешті ми додали достатній захист від потоків і серіалізації, але наш код виглядає об'ємно і некрасиво. Давайте давайте нашому герою заробляти
public final class Foo implements Serializable {
private static final long serialVersionUID = 1L;
// Wrapped in a inner static class so that loaded only when required
private static class FooLoader {
// And no more fear of threads
private static final Foo INSTANCE = new Foo();
}
// TODO add private shouting construcor
public static Foo getInstance() {
return FooLoader.INSTANCE;
}
// Damn you serialization
@SuppressWarnings("unused")
private Foo readResolve() {
return FooLoader.INSTANCE;
}
}
Так, це наш самий герой :)
Оскільки рядок private static final Foo INSTANCE = new Foo();
виконується лише тоді, коли клас FooLoader
фактично використовується, це піклується про ледачу інстанцію,
і чи гарантовано це безпечно для ниток.
І ми зайшли так далеко, ось найкращий спосіб досягти всього, що ми зробили найкращим чином
public enum Foo {
INSTANCE;
}
До якого внутрішньо будуть ставитися як
public class Foo {
// It will be our sole hero
private static final Foo INSTANCE = new Foo();
}
Це воно! Більше немає страху перед серіалізацією, нитками та потворним кодом. Також однолітні ENUMS ліниво ініціалізуються .
Цей підхід функціонально еквівалентний підходу на публічному полю, за винятком того, що він більш стислий, забезпечує механізм серіалізації безкоштовно та забезпечує гарантію залізобезпеки проти багаторазових інстанцій, навіть за умови складних серіалізаційних або рефлексійних атак. Хоча цей підхід ще не отримав широкого поширення, одноелементний тип enum - найкращий спосіб реалізувати синглтон.
-Joshua Блох в "Ефективна Java"
Тепер ви, можливо, зрозуміли, чому ENUMS вважається найкращим способом впровадження Singleton і дякую за ваше терпіння :)
Оновили це у своєму блозі .
serialVersionUID
з 0L
. Третя проблема: немає налаштування: Будь-які специфічні для класу записиObject, readObject, readObjectNoData, writeReplace та readResolve методи, визначені типами enum, ігноруються під час серіалізації та десеріалізації.
Рішення, розміщене Стю Томпсоном, дійсне в Java5.0 та пізніших версіях. Але я вважаю за краще не використовувати його, оскільки вважаю, що це схильність до помилок.
Забути мінливе твердження легко і важко зрозуміти, чому це необхідно. Без енергонезалежності цей код уже не був би безпечним для потоків через подвійну перевірку блокування антипаттера. Детальніше про це див. У пункті 16.2.4 Java Concurrency in Practice . Коротше кажучи: цей шаблон (до Java5.0 або без мінливого твердження) може повернути посилання на об'єкт "Бар", який (досі) знаходиться у неправильному стані.
Ця модель була придумана для оптимізації продуктивності. Але це насправді вже не є справжнім питанням. Наступний лінивий код ініціалізації - швидше і - що ще важливіше - легше читати.
class Bar {
private static class BarHolder {
public static Bar bar = new Bar();
}
public static Bar getBar() {
return BarHolder.bar;
}
}
getBar()
. (І якщо вас getBar
називають "занадто рано", тоді ви зіткнетеся з тією самою проблемою, незалежно від того, як реалізуються одиноки.) Ви можете побачити ліниве завантаження класу кодом вище тут: pastebin.com/iq2eayiR
Нитка безпечна в Java 5+:
class Foo {
private static volatile Bar bar = null;
public static Bar getBar() {
if (bar == null) {
synchronized(Foo.class) {
if (bar == null)
bar = new Bar();
}
}
return bar;
}
}
EDIT : Зверніть увагу на volatile
модифікатор тут. :) Це важливо, оскільки без нього JMM (Java Memory Model) не гарантує інші потоки, щоб побачити зміни його значення. Синхронізація цього не бере на себе, вона лише серіалізує доступ до цього блоку коду.
EDIT 2 : У відповіді @Bno детально описаний підхід, рекомендований Біллом П'ю (FindBugs), і це можна суперечити краще. Ідіть читайте і голосуйте також за його відповідь.
Забути ліниву ініціалізацію , це занадто проблематично. Це найпростіше рішення:
public class A {
private static final A INSTANCE = new A();
private A() {}
public static A getInstance() {
return INSTANCE;
}
}
Переконайтеся, що вам це справді потрібно. Зробіть Google для "однотонного анти-шаблону", щоб побачити деякі аргументи проти цього. Я думаю, що в цьому немає нічого поганого, але це лише механізм виявлення глобальних ресурсів / даних, тому переконайтеся, що це найкращий спосіб. Зокрема, я вважав, що введення залежності є більш корисним, особливо якщо ви також використовуєте одиничні тести, оскільки DI дозволяє використовувати знущаються ресурси для тестування.
Мене містифікують деякі відповіді, які пропонують DI як альтернативу використанню одиночних клавіш; це непов'язані поняття. Ви можете використовувати DI для введення екземплярів як одиночного, так і не одиночного (наприклад, за один потік). Принаймні це справедливо, якщо ви використовуєте Spring 2.x, я не можу говорити про інші рамки DI.
Тож моя відповідь на ОП була б (у всіх, окрім найбільш тривіального зразкового коду):
Цей підхід дає вам приємну роз'єднану (і, отже, гнучку та перевірену) архітектуру, де використання синглетона - це легко оборотна деталь реалізації (за умови, звичайно, що будь-які одинакові кнопки, які ви використовуєте, є безпечними для потоків).
TicketNumberer
який повинен мати єдиний глобальний екземпляр, і де ви хочете написати клас, TicketIssuer
який містить рядок коду int ticketNumber = ticketNumberer.nextTicketNumber();
. У традиційному одинарному мисленні попередній рядок коду мав би бути чимось подібним TicketNumberer ticketNumberer = TicketNumberer.INSTANCE;
. У міркуванні DI, клас мав би такий конструктор public TicketIssuer(TicketNumberer ticketNumberer) { this.ticketNumberer = ticketNumberer; }
.
main
метод програми (або один із її міньйонів) створив би залежність, а потім викликав конструктор. По суті, використання глобальної змінної (або глобального методу) є просто простою формою жахливого шаблона локатора обслуговування , і його можна замінити ін'єкцією залежності, як і будь-яке інше використання цього шаблону.
Дійсно, подумайте, для чого вам потрібен одинар, перш ніж писати його. Існує квазірелігійна суперечка щодо їх використання, яку ви можете легко наткнутися, якщо ви перейдете на Google в одиночку.
Особисто я намагаюся уникати синглів якомога частіше з багатьох причин, знову ж таки більшість з них можна знайти, гуглюючи однотонними. Я відчуваю, що досить часто одинаки зловживають, тому що їх легко зрозуміти всім, вони використовуються як механізм отримання "глобальних" даних в проект OO, і вони використовуються, тому що легко обійти управління життєвим циклом об'єкта (або дійсно думаючи про те, як можна зробити A зсередини B). Подивіться на такі речі, як Інверсія управління (IoC) або залежність впорскування (DI) для приємного середнього рівня.
Якщо вам справді потрібен, то у Вікіпедії є хороший приклад правильної реалізації сингтона.
Далі 3 різних підходу
1) Енум
/**
* Singleton pattern example using Java Enumj
*/
public enum EasySingleton{
INSTANCE;
}
2) Перевірена блокування / ледаче завантаження
/**
* Singleton pattern example with Double checked Locking
*/
public class DoubleCheckedLockingSingleton{
private static volatile DoubleCheckedLockingSingleton INSTANCE;
private DoubleCheckedLockingSingleton(){}
public static DoubleCheckedLockingSingleton getInstance(){
if(INSTANCE == null){
synchronized(DoubleCheckedLockingSingleton.class){
//double checking Singleton instance
if(INSTANCE == null){
INSTANCE = new DoubleCheckedLockingSingleton();
}
}
}
return INSTANCE;
}
}
3) Статичний заводський метод
/**
* Singleton pattern example with static factory method
*/
public class Singleton{
//initailzed during class loading
private static final Singleton INSTANCE = new Singleton();
//to prevent creating another instance of Singleton
private Singleton(){}
public static Singleton getSingleton(){
return INSTANCE;
}
}
Я використовую Spring Framework для управління своїми однотонними. Це не примушує "однотонність" класу (чого ви дійсно не можете зробити, якщо задіяні кілька завантажувачів класів), але забезпечує дійсно простий спосіб побудови та налаштування різних заводів для створення різних типів об'єктів.
Версія 1:
public class MySingleton {
private static MySingleton instance = null;
private MySingleton() {}
public static synchronized MySingleton getInstance() {
if(instance == null) {
instance = new MySingleton();
}
return instance;
}
}
Ледаче завантаження, безпека ниток із блокуванням, низька продуктивність через synchronized
.
Версія 2:
public class MySingleton {
private MySingleton() {}
private static class MySingletonHolder {
public final static MySingleton instance = new MySingleton();
}
public static MySingleton getInstance() {
return MySingletonHolder.instance;
}
}
Ледаче завантаження, безпека різьби з не блокуванням, висока продуктивність.
Якщо вам не потрібно ледаче завантаження, просто спробуйте
public class Singleton {
private final static Singleton INSTANCE = new Singleton();
private Singleton() {}
public static Singleton getInstance() { return Singleton.INSTANCE; }
protected Object clone() {
throw new CloneNotSupportedException();
}
}
Якщо ви бажаєте ледачого завантаження і хочете, щоб ваш Singleton був безпечним для потоків, спробуйте подвійну перевірку
public class Singleton {
private static Singleton instance = null;
private Singleton() {}
public static Singleton getInstance() {
if(null == instance) {
synchronized(Singleton.class) {
if(null == instance) {
instance = new Singleton();
}
}
}
return instance;
}
protected Object clone() {
throw new CloneNotSupportedException();
}
}
Оскільки модель подвійної перевірки не гарантовано працює (через певну проблему з компіляторами, я нічого про це не знаю.), Ви також можете спробувати синхронізувати весь метод getInstance або створити реєстр для всіх ваших Singletons.
volatile
Я б сказав, що Енум одинарний
Синглтон, використовуючи enum в Java, як правило, спосіб оголосити enum singleton. Enum singleton може містити змінну екземпляра та метод екземпляра. Для простоти також зауважте, що якщо ви використовуєте будь-який метод примірника, вам потрібно забезпечити безпеку потоку цього методу, якщо він взагалі впливає на стан об'єкта.
Використання перерахунку дуже легко здійснити і не має недоліків щодо об'єктів, що піддаються серіалізуванню, які необхідно обійти іншими способами.
/**
* Singleton pattern example using Java Enum
*/
public enum Singleton {
INSTANCE;
public void execute (String arg) {
//perform operation here
}
}
Ви можете отримати доступ до нього Singleton.INSTANCE
набагато простіше, ніж викликати getInstance()
метод на Singleton.
1.12 Серіялізація констант Енума
Константи Енума серіалізуються інакше, ніж звичайні об'єкти, що серіалізуються, або екстерналізації. Серіялізована форма константи перерахунку складається виключно з її назви; Польові значення константи у формі відсутні. Щоб серіалізувати константу enum,
ObjectOutputStream
записує значення, повернене методом імені константи enum. Щоб дезаріалізувати константу перерахунку,ObjectInputStream
читає ім'я постійної з потоку; деріаріалізована константа потім отримується за допомогою викликуjava.lang.Enum.valueOf
методу, передаючи тип перерахунку константи разом з отриманим постійним ім'ям в якості аргументів. Як і інші об'єкти, що серіалізуються, або константи, що перебувають у процесі екстерналізації, константи перерахунку можуть функціонувати як цілі зворотних посилань, що з'являються згодом у потоці серіалізації.Процес , з допомогою якого серіалізовать перерахування константи не може бути налаштований: будь-який клас конкретним
writeObject
,readObject
,readObjectNoData
,writeReplace
, іreadResolve
методи , визначені типами перерахувань ігноруються під час сериализации і десеріалізациі. Аналогічно, будь-якіserialPersistentFields
абоserialVersionUID
польові декларації також ігноруються - всі типи перерахувань мають фіксовануserialVersionUID
з0L
. Документування серіалізаційних полів та даних для типів перерахувань не є необхідним, оскільки не змінюється тип надісланих даних.
Ще одна проблема звичайних Singletons полягає в тому, що коли ви реалізуєте Serializable
інтерфейс, вони більше не залишаються Singleton, оскільки readObject()
метод завжди повертає новий екземпляр, як конструктор на Java. Цього можна уникнути, використовуючи readResolve()
та відкидаючи новостворений екземпляр, замінивши на одиночний, як показано нижче
// readResolve to prevent another instance of Singleton
private Object readResolve(){
return INSTANCE;
}
Це може стати ще складнішим, якщо ваш Singleton Class підтримуватиме стан, як вам потрібно зробити їх перехідними, але в Enum Singleton, серіалізація гарантована JVM.
Добре читайте
There are 4 ways to create a singleton in java.
1- eager initialization singleton
public class Test{
private static final Test test = new Test();
private Test(){}
public static Test getTest(){
return test;
}
}
2- lazy initialization singleton (thread safe)
public class Test {
private static volatile Test test;
private Test(){}
public static Test getTest() {
if(test == null) {
synchronized(Test.class) {
if(test == null){test = new Test();
}
}
}
return test;
}
3- Bill Pugh Singleton with Holder Pattern (Preferably the best one)
public class Test {
private Test(){}
private static class TestHolder{
private static final Test test = new Test();
}
public static Test getInstance(){
return TestHolder.test;
}
}
4- enum singleton
public enum MySingleton {
INSTANCE;
private MySingleton() {
System.out.println("Here");
}
}
Можливо, у цій грі трохи запізнюється, але є багато нюансів щодо реалізації одиночного. Шаблон тримача не можна використовувати в багатьох ситуаціях. І IMO при використанні летючої - вам також слід використовувати локальну змінну. Почнемо спочатку і переглянемо проблему. Ви побачите, що я маю на увазі.
Перша спроба може виглядати приблизно так:
public class MySingleton {
private static MySingleton INSTANCE;
public static MySingleton getInstance() {
if (INSTANCE == null) {
INSTANCE = new MySingleton();
}
return INSTANCE;
}
...
}
Тут ми маємо клас MySingleton, який має приватний статичний член під назвою INSTANCE, і публічний статичний метод, який називається getInstance (). Перший раз, коли викликається getInstance (), член INSTANCE є нульовим. Потім потік перейде до умови створення та створить новий екземпляр класу MySingleton. Подальші виклики до getInstance () виявлять, що змінна INSTANCE вже встановлена, і тому не створить інший екземпляр MySingleton. Це гарантує, що існує лише один екземпляр MySingleton, який поділяється між усіма абонентами getInstance ().
Але ця реалізація має проблеми. Багатопотокові програми матимуть умову гонки щодо створення єдиного екземпляра. Якщо декілька потоків виконання одночасно потрапляють на метод getInstance () (або навколо), кожен з них буде бачити член INSTANCE як нульовий. Це призведе до того, що кожен потік створює новий екземпляр MySingleton та згодом встановлює член INSTANCE.
private static MySingleton INSTANCE;
public static synchronized MySingleton getInstance() {
if (INSTANCE == null) {
INSTANCE = new MySingleton();
}
return INSTANCE;
}
Тут ми використовували ключове слово синхронізований у підписі методу для синхронізації методу getInstance (). Це, безумовно, виправить наш стан гонки. Нитки тепер блокуються та вводять метод по черзі. Але це також створює проблеми з продуктивністю. Ця програма не тільки синхронізує створення єдиного примірника, але синхронізує всі виклики до getInstance (), включаючи читання. Читання не потрібно синхронізувати, оскільки вони просто повертають значення INSTANCE. Оскільки читання становлять основну частину наших дзвінків (пам’ятайте, що екземпляри трапляються лише під час першого дзвінка), ми спричинимо непотрібну ефективність, синхронізувавши весь метод.
private static MySingleton INSTANCE;
public static MySingleton getInstance() {
if (INSTANCE == null) {
synchronize(MySingleton.class) {
INSTANCE = new MySingleton();
}
}
return INSTANCE;
}
Тут ми перенесли синхронізацію від підпису методу до синхронізованого блоку, який завершує створення екземпляра MySingleton. Але чи вирішує це наша проблема? Ну, ми більше не блокуємо читання, але ми також зробили крок назад. Кілька потоків будуть потрапляти в метод getInstance () приблизно в один і той же час і приблизно, і всі вони побачать член INSTANCE як нульовий. Потім вони потраплять на синхронізований блок, де можна отримати замок і створити екземпляр. Коли цей потік вийде з блоку, інші потоки змагатимуться за замок, і одна за одною кожна нитка пройде через блок і створить новий екземпляр нашого класу. Тож ми повертаємось туди, з чого почали.
private static MySingleton INSTANCE;
public static MySingleton getInstance() {
if (INSTANCE == null) {
synchronized(MySingleton.class) {
if (INSTANCE == null) {
INSTANCE = createInstance();
}
}
}
return INSTANCE;
}
Тут ми видаємо черговий чек від блоку INSIDE. Якщо член INSTANCE вже встановлений, ми пропустимо ініціалізацію. Це називається подвійним перевіреним блокуванням.
Це вирішує нашу проблему множинних інстанцій. Але ще раз наше рішення поставило ще один виклик. Інші потоки можуть не "бачити", що член INSTANCE був оновлений. Це пояснюється тим, як Java оптимізує операції з пам'яттю. Нитками копіюються вихідні значення змінних з основної пам'яті в кеш процесора. Зміни значень потім записуються в цей кеш і читаються з нього. Це особливість Java, призначена для оптимізації продуктивності. Але це створює проблему для нашої синхронної реалізації. Другий потік - обробляється іншим процесором або ядром, використовуючи інший кеш - не побачить змін, внесених першим. Це призведе до того, що другий потік побачить член INSTANCE як нульовий примушує створити новий екземпляр нашого сингтона.
private static volatile MySingleton INSTANCE;
public static MySingleton getInstance() {
if (INSTANCE == null) {
synchronized(MySingleton.class) {
if (INSTANCE == null) {
INSTANCE = createInstance();
}
}
}
return INSTANCE;
}
Ми вирішуємо це за допомогою мінливого ключового слова в декларації члена ІНСТАНЦІЇ. Це дозволить компілятору завжди читати з головної пам’яті та записувати її в основну пам'ять, а не кеш процесора.
Але ця проста зміна приходить дорожче. Оскільки ми обходимо кеш-пам'ять процесора, ми будемо приймати показник продуктивності кожного разу, коли ми працюємо з летючим членом INSTANCE - що ми робимо 4 рази. Ми ще раз перевіряємо існування (1 і 2), встановлюємо значення (3), а потім повертаємо значення (4). Можна стверджувати, що цей шлях є випадком бахроми, оскільки ми створюємо екземпляр лише під час першого виклику методу. Можливо, ударний твір на творчість є допустимим. Але навіть наш основний випадок використання, читається, буде працювати на летючий член двічі. Раз перевірити існування і знову повернути його значення.
private static volatile MySingleton INSTANCE;
public static MySingleton getInstance() {
MySingleton result = INSTANCE;
if (result == null) {
synchronized(MySingleton.class) {
result = INSTANCE;
if (result == null) {
INSTANCE = result = createInstance();
}
}
}
return result;
}
Оскільки хіт продуктивності пов’язаний з функціонуванням безпосередньо на летючому елементі, давайте встановимо локальну змінну до значення летючого та замість цього будемо працювати на локальній змінній. Це зменшить кількість разів, коли ми працюємо на енергонезалежних, тим самим відновивши частину втраченої роботи. Зауважте, що нам доведеться знову встановити нашу локальну змінну, коли ми входимо в синхронізований блок. Це гарантує, що це в курсі будь-яких змін, які відбулися, поки ми чекали блокування.
Я недавно написав статтю про це. Деконструкція Сінглтона . Ви можете знайти більше інформації про ці приклади та приклад шаблону "власника" там. Існує також приклад у реальному світі, що демонструє перевірений непостійний підхід. Сподіваюсь, це допомагає.
class MySingleton
- може, так і має бути final
?
BearerToken
Екземпляр не є статичним, оскільки він є частиною, BearerTokenFactory
яка налаштована на певний сервер авторизації. BearerTokenFactory
Об'єктів може бути багато - кожен має власний "кеш-пам'ять", BearerToken
який він роздає, поки термін його дії не закінчиться. hasExpired()
Метод на BeraerToken
називаються в фабриках get()
метод , щоб гарантувати її не роздає втрачає маркер. Якщо термін дії закінчився, на сервер авторизації буде запрошено новий маркер. Абзац після кодового блоку пояснює це детальніше.
Ось як реалізувати просте singleton
:
public class Singleton {
// It must be static and final to prevent later modification
private static final Singleton INSTANCE = new Singleton();
/** The constructor must be private to prevent external instantiation */
private Singleton(){}
/** The public static method allowing to get the instance */
public static Singleton getInstance() {
return INSTANCE;
}
}
Ось як правильно лінь створити своє singleton
:
public class Singleton {
// The constructor must be private to prevent external instantiation
private Singleton(){}
/** The public static method allowing to get the instance */
public static Singleton getInstance() {
return SingletonHolder.INSTANCE;
}
/**
* The static inner class responsible for creating your instance only on demand,
* because the static fields of a class are only initialized when the class
* is explicitly called and a class initialization is synchronized such that only
* one thread can perform it, this rule is also applicable to inner static class
* So here INSTANCE will be created only when SingletonHolder.INSTANCE
* will be called
*/
private static class SingletonHolder {
private static final Singleton INSTANCE = new Singleton();
}
}
getInstance()
. Але дійсно, якщо у вас немає будь-яких інших статичних методів у вашому класі, Singleton
і ви лише дзвоните getInstance()
, реальної різниці немає.
Вам потрібна подвійна перевірка ідіоми, якщо вам потрібно лінько завантажити змінну екземпляра класу. Якщо вам потрібно ліниво завантажити статичну змінну або однотонну , вам потрібна ініціалізація на ідіому власника попиту .
Крім того, якщо синглтон повинен бути серіалізним, всі інші поля повинні бути перехідними, а метод readResolve () повинен бути реалізований для підтримки єдиного інваріантного об'єкта. В іншому випадку, кожного разу, коли об'єкт дезаріалізується, буде створений новий екземпляр об'єкта. Що readResolve () робить, це замінити новий об’єкт, прочитаний readObject (), який змусив цей новий об'єкт збирати сміття, оскільки немає змінної, що посилається на нього.
public static final INSTANCE == ....
private Object readResolve() {
return INSTANCE; // original singleton instance.
}
Різні способи зробити одиночний об’єкт:
За Джошуа Блохом - Енум був би найкращим.
ви також можете використовувати подвійне блокування перевірки.
Можна використовувати навіть внутрішній статичний клас.
Енум одиночний
Найпростішим способом реалізації Singleton, який є безпечним для потоків, є використання Enum
public enum SingletonEnum {
INSTANCE;
public void doSomething(){
System.out.println("This is a singleton");
}
}
Цей код працює з моменту впровадження Enum в Java 1.5
Перевірено блокування
Якщо ви хочете кодувати "класичний" синглтон, який працює у багатопотоковому середовищі (починаючи з Java 1.5), слід скористатися цим.
public class Singleton {
private static volatile Singleton instance = null;
private Singleton() {
}
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class){
if (instance == null) {
instance = new Singleton();
}
}
}
return instance ;
}
}
Це не є безпечним для потоків до 1,5, оскільки реалізація летючого ключового слова була різною.
Раннє завантаження Singleton (працює навіть до Java 1.5)
Ця реалізація створює одиночне зображення при завантаженні класу та забезпечує безпеку потоку.
public class Singleton {
private static final Singleton instance = new Singleton();
private Singleton() {
}
public static Singleton getInstance() {
return instance;
}
public void doSomething(){
System.out.println("This is a singleton");
}
}
Для JSE 5.0 і вище використовуйте підхід Enum, в іншому випадку використовуйте статичний підхід для одноразового тримання ((ледачий підхід для завантаження, описаний Біллом П'ю). Шкільне рішення також є безпечним для потоків, не вимагаючи спеціальних мовних конструкцій (тобто мінливих або синхронізованих).
Інший аргумент, який часто застосовується проти Сінглтон, - це їх проблеми зі встановленням. Для проведення тестування одинарних нелегко піддаються вибору. Якщо це виявляється проблемою, я люблю вносити такі незначні зміни:
public class SingletonImpl {
private static SingletonImpl instance;
public static SingletonImpl getInstance() {
if (instance == null) {
instance = new SingletonImpl();
}
return instance;
}
public static void setInstance(SingletonImpl impl) {
instance = impl;
}
public void a() {
System.out.println("Default Method");
}
}
Доданий setInstance
метод дозволяє встановити макет виконання одного класу під час тестування:
public class SingletonMock extends SingletonImpl {
@Override
public void a() {
System.out.println("Mock Method");
}
}
Це також працює з підходами ранньої ініціалізації:
public class SingletonImpl {
private static final SingletonImpl instance = new SingletonImpl();
private static SingletonImpl alt;
public static void setInstance(SingletonImpl inst) {
alt = inst;
}
public static SingletonImpl getInstance() {
if (alt != null) {
return alt;
}
return instance;
}
public void a() {
System.out.println("Default Method");
}
}
public class SingletonMock extends SingletonImpl {
@Override
public void a() {
System.out.println("Mock Method");
}
}
Це має і недолік піддавати цю функціональність звичайному додатку. Інші розробники, що працюють над цим кодом, можуть спокусити використати метод «setInstance», щоб змінити певну функцію і, таким чином, змінити всю поведінку програми, тому цей метод повинен містити принаймні гарне попередження у своєму javadoc.
Тим не менш, для можливості тестування макета (при необхідності) ця експозиція коду може бути прийнятною ціною.
найпростіший одиночний клас
public class Singleton {
private static Singleton singleInstance = new Singleton();
private Singleton() {}
public static Singleton getSingleInstance() {
return singleInstance;
}
}
Я все ще думаю, що після java 1.5, enum є найкращою доступною синглтонною реалізацією, оскільки це також забезпечує те, що навіть у багатопотокових середовищах створюється лише один екземпляр.
public enum Singleton{
INSTANCE;
}
і ви зробили !!!
Погляньте на цю публікацію.
Приклади шаблонів дизайну GoF в основних бібліотеках Java
З розділу найкращої відповіді "Сінглтон",
Синглтон (впізнаваний за допомогою творчих методів, щоразу повертаючи один і той же екземпляр (як правило, сам))
- java.lang.Runtime # getRuntime ()
- java.awt.Desktop # getDesktop ()
- java.lang.System # getSecurityManager ()
Ви також можете вивчити приклад Singleton з самих рідних класів Java.
Найкращий одинарний зразок, який я коли-небудь бачив, використовує інтерфейс постачальника.
Дивись нижче:
public class Singleton<T> implements Supplier<T> {
private boolean initialized;
private Supplier<T> singletonSupplier;
public Singleton(T singletonValue) {
this.singletonSupplier = () -> singletonValue;
}
public Singleton(Supplier<T> supplier) {
this.singletonSupplier = () -> {
// The initial supplier is temporary; it will be replaced after initialization
synchronized (supplier) {
if (!initialized) {
T singletonValue = supplier.get();
// Now that the singleton value has been initialized,
// replace the blocking supplier with a non-blocking supplier
singletonSupplier = () -> singletonValue;
initialized = true;
}
return singletonSupplier.get();
}
};
}
@Override
public T get() {
return singletonSupplier.get();
}
}
Іноді простого " static Foo foo = new Foo();
" недостатньо. Подумайте лише про деякі основні вставки даних, які ви хочете зробити.
З іншого боку, вам доведеться синхронізувати будь-який метод, який створює змінну singleton як таку. Синхронізація не є поганою як такою, але вона може призвести до проблем продуктивності або блокування (у дуже рідкісних ситуаціях, використовуючи цей приклад. Рішення полягає в
public class Singleton {
private static Singleton instance = null;
static {
instance = new Singleton();
// do some of your instantiation stuff here
}
private Singleton() {
if(instance!=null) {
throw new ErrorYouWant("Singleton double-instantiation, should never happen!");
}
}
public static getSingleton() {
return instance;
}
}
Тепер що відбувається? Клас завантажується через завантажувач класів. Безпосередньо після інтерпретації класу з байтового масиву VM виконує статичний {} - блок. у цьому весь секрет: статичний блок викликається лише один раз, час, коли даний клас (ім'я) даного пакета завантажується цим завантажувачем одного класу.
public class Singleton {
private static final Singleton INSTANCE = new Singleton();
private Singleton(){
if (INSTANCE != null)
throw new IllegalStateException (“Already instantiated...”);
}
public synchronized static Singleton getInstance() {
return INSTANCE;
}
}
Оскільки ми додали ключове слово синхронізований перед getInstance, ми уникнули умови перегонів у випадку, коли два потоки викликають getInstance одночасно.