Назначение
Singleton обеспечивает наличие в системе только одного экземпляра заданного класса, позволяя другим классам получать доступ к этому экземпляру.
- нужно, имея лишь один экземпляр класса, обеспечить доступ к нему всем элементам приложения и запретить создание новых экземпляров этого класса.
Описание
Допустим, имеется некоторая центральная функциональность, которая должна быть разделена на всё приложение. Такой функциональностью может быть, например, сбор статистики (истории) или управление единственным ресурсом. Эта функциональность должна быть реализована классом, единственный экземпляр которого используется всеми клиентами внутри приложения. Для избежания несогласованного состояния недопустимо позволять клиентам самостоятельно создавать экземпляры этого класса. С другой стороны, каждому клиенту необходимо предоставить доступ к единственному объекту.
Реализация
Во-первых, необходимо запретить создавать объекты класса. Для этого надо определить в
классе только private
конструктор(ы). Это также ведёт к потере возможности
наследования такого класса. Для получения единственного экземпляра класса
добавляется public static
метод getInstance()
, возвращающий единственный объект
singleton класса. Ссылка на этот объект хранится как private static
поле класса. Как правило, создание singleton объекта
происходит при инициализации класса, но возможно и “lazy” инстанциирование,
когда создание экземпляра происходит в момент первого вызова getInstance()
.
Опасности:
- все конструкторы обязательно должны быть объявлены
private
и причем должен быть как минимум один конструктор, иначе Java добавит конструктор по-умолчанию; - класс не должен допускать альтернативных возможностей создания своих
экземпляров: реализовывать интерфейсы
Cloneable
илиSerializable
; - внимательно надо относиться к реализации Singleton в условиях, когда программа ссылается на Singleton только через динамически загружаемые классы: апплеты, сервлеты, мидлеты. Если программа не использует классы, то они могут быть удалены сборщиком мусора. Когда Singleton будет загружен снова, то информация в старом экземпляре будет потеряна;
- потокобезопасность.
-
потокобезопасность является проблемой только для ленивой инициализации; для инициализации при загрузке
класса и варианта реализации через
enum
потокобезопасность достигается из коробки; -
для ленивой инициализации при одновременном доступе нескольких потоков
возможно создание нескольких экземпляров Singleton. Решения:
- метод
getInstance()
объявить какsynchronized
; - применить идиому Double Checked Locking & volatile;
- применить идиому On Demand Holder idiom;
- метод
- на потокобезопасность надо обращать внимание не только в контексте инициализации Singleton, но и в контексте вызова других методов, так как если существует единственный объект, то все потоки используют только этот объект.
private static final
поле типа Singleton, ссылка на тот самый единственный экземпляр, который создается при инициализации класса (как правило)private
конструктор (часто пустой) для защиты от создания новых объектовpublic static
метод для доступа к единственному экземпляру класса, т.е. возвращает ссылку на instance
Примеры
- Класс
java.lang.Runtime
имеет единственныйprivate
конструктор с комментарием/** Don't let anyone else instantiate this class */
и статический методgetRuntime()
, который возвращаетprivate static final
экземплярcurrentRuntime
. - Может использоваться например для реализации Null Object. Так как Null Object обычно всё равно не содержит никакой информации о состоянии, то для него подойдет реализация в виде Singleton.
Варианты
- “Канонический”, не ленивый: создание экземпляра при инициализации класса:
public class Singleton { private static final Singleton instance = new Singleton(); private Singleton(){} public static Singleton getInstance() { return instance; } }
- Ленивый (потоконебезопасный) Singleton:
instance
создается не при инициализации класса, а лениво, во время вызоваgetInstance()
:// ... public static Singleton getInstance() { if (instance == null) { instance = new Singleton(); } return instance; }
- Ленивый (потокобезопасный) Singleton через
synchronized
. Добавлен модификаторsynchronized
к методуgetInstance()
. - Ленивый (потокобезопасный) Singleton через идиому Double Checked Locking & volatile
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; } }
- Ленивый (потокобезопасный) Singleton через идиому On Demand Holder idiom
public class Singleton { private static class SingletonHolder { public static final Singleton HOLDER_INSTANCE = new Singleton(); } public static Singleton getInstance() { return SingletonHolder.HOLDER_INSTANCE; } }
Идиома основана на том, что класс инициализируется только при первом активном использовании (см. справку). То есть, инициализация класса
Singleton
выполняется тривиально (в нем нетstatic
переменных илиstatic
блоков инициализации) и не вызывает инициализациюSingletonHolder
. ИнициализацияSingletonHolder
выполняется в момент использования статического поляHOLDER_INSTANCE
внутри методаgetInstance()
. JLS гарантирует последовательную, не многопоточную инициализацию класса. - Реализация через
enum
, не ленивый (предпочтительна по Effective Java by Joshua Bloch):public enum Singleton { INSTANCE; public void execute(String arg){ // perform operation here } }
- Допустимо несколько экземпляров Singleton. Метод
getInstance()
управляет распределением экземпляров и иногда может создавать новые. Обобщается до шаблона ObjectPool. - Метод
getInstance()
может возвращать подклассы Singleton.
Справка
Следует различать инициализацию объектов класса, инициализацию классов и загрузку классов.
Загрузка класса - чтение его бинарного представления в JVM. Момент загрузки класса явно не специфицирован, известно лишь что она происходит до инициализации класса. Загрузка может быть ленивой или нет в зависимости от JVM.
Инициализация класса - установка начальных значений static
переменных и выполнение
кода в static
блоках. Момент инициализации класса - первое активное
использование класса:
- создается экземпляр класса (в том числе через reflection, клонирование, десериализацию)
- вызывается статический метод класса
- присваивается значение статической переменной, объявленной в классе
- используется статическое поле, если это не константа
- выполняется выражение
assert
, вложенное лексически в класс верхнего уровня
Ссылки
https://java-design-patterns.com/patterns/singleton/
https://github.com/iluwatar/java-design-patterns/tree/master/singleton
StackOverflow: Examples of singleton classes in the Java APIs
Хабр: Правильный Singleton в Java
Wikipedia: Initialization-on-demand holder idiom