Это первая из серии статей, которые я хочу написать о шаблонах проектирования. Сегодня речь пойдёт о шаблоне Singleton.
Для начала хочу сказать, что такое вообще Singleton и когда он может быть полезен. Это шаблон, который обеспечивает существование одного экземпляра класса без возможности прямого создания этого класса. Например, в приложении может быть только один менеджер работы с базой данных.
Singleton используют в других шаблонах проектирования, таких как «абстрактная фабрика» (Abstract Factory), Строитель (Builder), Прототип (Prototype), Фасад(Facade) и т.д.
Этот шаблон также используется и самой Java в ее ядре, например в java.lang.Runtime и java.awt.Desktop.
На данный момент существуют несколько вариантов реализации со своими недостатками и преимуществами.
Вариант 1
Реализация выше отлично работает, если используется только один поток. Но если дело доходит до многопоточных систем, то это может вызвать проблемы, когда внутри цикла одновременно присутствуют несколько потоков. В этом случае теряется суть шаблона Singleton и два разных потока будут получать разные объекты класса.
Для решения этой проблемы есть следующий вариант.
Вариант 2
Решение подходит для многопоточных приложений. Но мы потеряли две вещи:
- Ленивую инициализацию (Объект instance будет создан classloader-ом во время инициализации класса)
- возможность обработки исключений(exceptions) во время вызова конструктора
Вариант 3
Использование внутреннего класса(решение Била Пью(Bill Pugh) “Initialization on Demand Holder”).
+ Ленивая инициализация, т.к. объект инициализируется при первом вызове метода getInstance().
+ Высокая производительность
- Проблема с обработкой исключительных ситуаций в конструкторе
- Невозможно использовать для не статических полей класса
Вариант 4
Synchronized Accessor
У этого варианта есть один недостаток. Синхронизация полезна только один раз, при первом обращении к getInstance(), после этого каждый раз, при обращении этому методу, синхронизация просто забирает время. Но если вызов getInstance() не происходит достаточно часто, то этот метод имеет преимущество перед остальными.
+ Ленивая инициализация
+ Есть возможность обрабатывать исключительные ситуации в конструкторе
- Низкая производительность (критическая секция) в наиболее типичном доступе
Вариант 5
Double Checked Locking & volatile
+ Ленивая инициализация
+ Высокая производительность
- Поддерживается только с JDK 1.5
Double Checked Lock можно использовать без исключений с immutable объектами (String, Integer, Float, и т.д.).
Вариант 6
Singleton с помощью Enum
С тех пор как Enum значения могут иметь глобальный доступ они используются в Singleton. Недостатком является то, что возвращаемый Enum тип, несколько негибкий, например, не поддерживает ленивую инициализацию.
+ Сериализация из коробки
+ Потокобезопасность из коробки
+ Возможность использования EnumSet, EnumMap и т.д.
+ Поддержка switch
- Не ленивая инициализация
Вот, собственно, и все распространенные варианты имплементаций данного паттерна.
Заключение
Можно выделить следующие советы по использованию того или иного подхода для реализации шаблона Singleton:
1) Использовать нормальную (не ленивую) инициализацию везде где это возможно;
2) Для статических полей использовать On Demand Holder idom;
3) Для простых полей использовать Double Chedked Lock & volatile idom;
4) Во всех остальных случаях использовать Syncronized accessor;
Для начала хочу сказать, что такое вообще Singleton и когда он может быть полезен. Это шаблон, который обеспечивает существование одного экземпляра класса без возможности прямого создания этого класса. Например, в приложении может быть только один менеджер работы с базой данных.
Singleton используют в других шаблонах проектирования, таких как «абстрактная фабрика» (Abstract Factory), Строитель (Builder), Прототип (Prototype), Фасад(Facade) и т.д.
Этот шаблон также используется и самой Java в ее ядре, например в java.lang.Runtime и java.awt.Desktop.
На данный момент существуют несколько вариантов реализации со своими недостатками и преимуществами.
Вариант 1
1 2 3 4 5 6 7 8 9 10 11 12 | class Singleton { private static Singleton instance; private Singleton(){ } public static Singleton getInstance(){ if(instance == null){ instance = new Singleton(); } return instance; } } |
Реализация выше отлично работает, если используется только один поток. Но если дело доходит до многопоточных систем, то это может вызвать проблемы, когда внутри цикла одновременно присутствуют несколько потоков. В этом случае теряется суть шаблона Singleton и два разных потока будут получать разные объекты класса.
Для решения этой проблемы есть следующий вариант.
Вариант 2
1 2 3 4 5 6 7 8 9 10 | class Singleton { private static Singleton instance = new Singleton(); private Singleton(){ } public static Singleton getInstance(){ return instance; } } |
Решение подходит для многопоточных приложений. Но мы потеряли две вещи:
- Ленивую инициализацию (Объект instance будет создан classloader-ом во время инициализации класса)
- возможность обработки исключений(exceptions) во время вызова конструктора
Вариант 3
Использование внутреннего класса(решение Била Пью(Bill Pugh) “Initialization on Demand Holder”).
1 2 3 4 5 6 7 8 9 10 | public class Singleton { public static class SingletonHolder { public static final Singleton HOLDER_INSTANCE = new Singleton(); } public static Singleton getInstance() { return SingletonHolder.HOLDER_INSTANCE; } } |
+ Ленивая инициализация, т.к. объект инициализируется при первом вызове метода getInstance().
+ Высокая производительность
- Проблема с обработкой исключительных ситуаций в конструкторе
- Невозможно использовать для не статических полей класса
Вариант 4
Synchronized Accessor
1 2 3 4 5 6 7 8 9 10 | public class Singleton { private static Singleton instance; public static synchronized Singleton getInstance() { if (instance == null) { instance = new Singleton(); } return instance; } } |
У этого варианта есть один недостаток. Синхронизация полезна только один раз, при первом обращении к getInstance(), после этого каждый раз, при обращении этому методу, синхронизация просто забирает время. Но если вызов getInstance() не происходит достаточно часто, то этот метод имеет преимущество перед остальными.
+ Ленивая инициализация
+ Есть возможность обрабатывать исключительные ситуации в конструкторе
- Низкая производительность (критическая секция) в наиболее типичном доступе
Вариант 5
Double Checked Locking & volatile
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | public class Singleton { private static volatile Singleton instance; public static Singleton getInstance() { Singleton localInstance = instance; if (localInstance == null) { synchronized (Singleton.class) { localInstance = instance; if (localInstance == null) { instance = localInstance = new Singleton(); } } } return localInstance; } } |
+ Ленивая инициализация
+ Высокая производительность
- Поддерживается только с JDK 1.5
Double Checked Lock можно использовать без исключений с immutable объектами (String, Integer, Float, и т.д.).
Вариант 6
Singleton с помощью Enum
С тех пор как Enum значения могут иметь глобальный доступ они используются в Singleton. Недостатком является то, что возвращаемый Enum тип, несколько негибкий, например, не поддерживает ленивую инициализацию.
1 2 3 4 5 6 7 8 | public enum EnumSingleton { INSTANCE; public static void doSomething(){ //do something } } |
+ Сериализация из коробки
+ Потокобезопасность из коробки
+ Возможность использования EnumSet, EnumMap и т.д.
+ Поддержка switch
- Не ленивая инициализация
Вот, собственно, и все распространенные варианты имплементаций данного паттерна.
Заключение
Можно выделить следующие советы по использованию того или иного подхода для реализации шаблона Singleton:
1) Использовать нормальную (не ленивую) инициализацию везде где это возможно;
2) Для статических полей использовать On Demand Holder idom;
3) Для простых полей использовать Double Chedked Lock & volatile idom;
4) Во всех остальных случаях использовать Syncronized accessor;
Комментариев нет:
Отправить комментарий