Что такое singleton java

Правильный Singleton в Java

Уверен, каждый из читателей, знает что такое шаблон проектирования “Singleton”, но не каждый знает как его программировать эффективно и правильно. Данная статья является попыткой агрегирования существующих знаний по этому вопросу.

Кроме того, можно рассматривать статью как продолжение замечательного исследования, публиковавшегося на Хабрахабре ранее.

Неленивый Singleton в Java

Автору известно два способа реализации шаблона с нормальной инициализацией.

1 Static field

+ Простая и прозрачная реализация
+ Потокобезопасность
Не ленивая инициализация

2 Enum Singleton

По мнению Joshua Bloch’а это лучший способ реализации шаблона [1].

+ Остроумно
+ Сериализация из коробки
+ Потокобезопасность из коробки
+ Возможность использования EnumSet, EnumMap и т.д.
+ Поддержка switch
Не ленивая инициализация

Ленивый Singleton в Java

На момент написания статьи существует как минимум три корректных реализации шаблона Singleton с ленивой инициализацией на Java.

1 Synchronized Accessor

+ Ленивая инициализация
Низкая производительность (критическая секция) в наиболее типичном доступе

2 Double Checked Locking & volatile

+ Ленивая инициализация
+ Высокая производительность
Поддерживается только с JDK 1.5 [5]

2.1 Почему не работает без volatile?

Проблема идиомы Double Checked Lock заключается в модели памяти Java, точнее в порядке создания объектов. Можно условно представить этот порядок следующими этапами [2, 3]:

Пусть мы создаем нового студента: Student s = new Student(), тогда

1) local_ptr = malloc(sizeof(Student)) // выделение памяти под сам объект;
2) s = local_ptr // инициализация указателя;
3) Student::ctor(s); // конструирование объекта (инициализация полей);

Таким образом, между вторым и третьим этапом возможна ситуация, при которой другой поток может получить и начать использовать (на основании условия, что указатель не нулевой) не полностью сконструированный объект. На самом деле, эта проблема была частично решена в JDK 1.5 [5], однако авторы JSR-133 [5] рекомендуют использовать voloatile для Double Cheсked Lock. Более того, их отношение к подобным вещам легко прослеживается из коментария к спецификации:

There exist a number of common but dubious coding idioms, such as the double-checked locking idiom, that are proposed to allow threads to communicate without synchronization. Almost all such idioms are invalid under the existing semantics, and are expected to remain invalid under the proposed semantics.

Таким образом, хотя проблема и решена, использовать Double Checked Lock без volatile крайне опасно. В некоторых случаях, зависящих от реализации JVM, операционной среды, планировщика и т.д., такой подход может не работать. Однако, серией опытов сопровождаемых просмотром ассемблерного кода, генерированного JIT’ом автору, такой случай вопросизвести не удалось.

Наконец, Double Checked Lock можно использовать без исключений с immutable объектами (String, Integer, Float, и т.д.).

3 On Demand Holder idiom

+ Ленивая инициализация
+ Высокая производительность
Невозможно использовать для не статических полей класса

Performance

Для сравнения производительности выше рассмотренных методов, была использована микро-бенчмарка [6], определяющая количество элементарных операций (инкремент поля) в секунду над Singleton объектом, из двух параллельных потоков.

Измерения производились на двухядерной машине Intel Core 2 Duo T7300 2GHz, 2Gb ram и Java HotSpot(TM) Client VM (build 17.0-b17). За единицу скора считается количество инкрементов (а следовательно и захватов объекта) в секунду * 100 000.

(больше — лучше)

ClientServer
Synchronized accessor42,686,3
Double Checked Lock & volatile179,8202,4
On Demand Holder181,6202,7

Вывод: если правильно подобрать реализацию шаблона можно получить ускорение (speed up) от до .

Summary

Можно выделить следующие короткие советы по использованию того или иного подхода для реализации шаблона “Одиночка” [1].

1) Использовать нормальную (не ленивую) инициализацию везде где это возможно;
2) Для статических полей использовать On Demand Holder idom;
3) Для простых полей использовать Double Chedked Lock & volatile idom;
4) Во всех остальных случаях использовать Syncronized accessor;

Java Class Library & Singleton

Примечательно, что разработчики Java Class Library выбрали наиболее простой способ реализации шаблона — Syncronized Accessor. C одной стороны — это гарантия совместимости и правильной работы. С другой — это потеря процессорного времени на вход и выход из критической секции при каждом обращении.

Быстрый поиск grep’ом по исходникам дал понять, что таких мест в JCL очень много.

Возможно следующая статья будет “Что будет если в Java Class Library правильно написать все Singleton классы?” 🙂

Источник

Реализация Singleton в JAVA

В этой статье я хочу затронуть тему одного из наиболее распространенных паттернов объектно-ориентированного программирования – Singleton. Но в данном случае я не буду описывать преимущества/недостатки и области применения этого паттерна, а попытаюсь изложить свой взгляд на его имплементацию в JAVA.

Общие сведения
Паттерн Singleton гарантирует, что у класса есть только один экземпляр, и предоставляет к нему глобальную точку доступа.

Область применения
1.) В системе должно существовать не более одного экземпляра заданного класса.
2.) Экземпляр должен быть легко доступен для всех клиентов данного класса.
3.) Создание объекта on demand, то есть, когда он понадобится первый раз, а не во время инициализации системы.

Реализация (JAVA):
На данный момент существуют несколько вариантов реализации со своими недостатками и преимуществами. В них мы и попробуем сейчас разобраться.

Вариант первый – самый простой, который приходит в голову сразу после понимания проблемы.
Что такое singleton java. Смотреть фото Что такое singleton java. Смотреть картинку Что такое singleton java. Картинка про Что такое singleton java. Фото Что такое singleton java

У этого решения есть единственный недостаток – оно не работает в многопоточной среде и поэтому не подходит в большинстве случаев. Решение подходит исключительно для однопоточных приложений.

Не беда, скажите вы, и предложите следующее решение.

Вариант второй:
Что такое singleton java. Смотреть фото Что такое singleton java. Смотреть картинку Что такое singleton java. Картинка про Что такое singleton java. Фото Что такое singleton java

И вы будете правы, так как проблему многопоточности мы решили, но потеряли две важные вещи:
1. Ленивую инициализацию (Объект instance будет создан classloader-ом во время инициализации класса)
2. Отсутствует возможность обработки исключительных ситуаций(exceptions) во время вызова конструктора.

Решение подходит для многопоточных приложений, при условии отсутствия опасности возникновения исключительных ситуаций в конструкторе и отсутствии необходимости ленивой инициализации.

Далее возникают 2 варианта решения.
1.) Использование внутреннего класса(решение Била Пью(Bill Pugh) “Initialization on Demand Holder”).
2.) Использование синхронизации.

Вариант третий:
“Initialization on Demand Holder”
Что такое singleton java. Смотреть фото Что такое singleton java. Смотреть картинку Что такое singleton java. Картинка про Что такое singleton java. Фото Что такое singleton java

В данном случае мы полностью решили проблему ленивой инициализации – объект инициализируется при первом вызове метода getInstance(). Но у нас осталась проблема с обработкой исключительных ситуаций в конструкторе. Так что, если конструктор класса не вызывает опасений создания исключительных ситуаций, то смело можно использовать этот метод.

Синхронизация
Этой части я хотел бы уделить особое внимание. Можно было бы подойти к данному вопросу с заголовком «synchronized – мифы и реальность».

И так, самый прямолинейный метод.

Вариант четвертый:
Что такое singleton java. Смотреть фото Что такое singleton java. Смотреть картинку Что такое singleton java. Картинка про Что такое singleton java. Фото Что такое singleton java
У этого варианта есть только один недостаток. Синхронизация полезна только один раз, при первом обращении к getInstance(), после этого каждый раз, при обращении этому методу, синхронизация просто забирает время. Что можно сказать по этому поводу? Ну, во-первых, если вызов getInstance() не происходит достаточно часто (что значит «достаточно часто» решать вам), то этот метод имеет преимущество перед остальными – прост, понятен, лениво инициализируется, дает возможность обрабатывать исключительные ситуации в конструкторе. А во-вторых, синхронизация в Java перестала быть обременительно медленной настолько, насколько ее боятся. Ну что еще для счастья надо?

Теперь рассмотрим вариант синхронизированного решения, в котором попытаемся решить проблему, возникшую в предыдущем варианте.

Наиболее распространенный способ — «Double-Checked Locking». В своем оригинальном варианте:
Что такое singleton java. Смотреть фото Что такое singleton java. Смотреть картинку Что такое singleton java. Картинка про Что такое singleton java. Фото Что такое singleton java

Не работает! Почему? Это отдельная тема, но для интересующихся могу посоветовать прочитать эту статью http://www.cs.umd.edu/

Но не надо совсем отчаиваться, в JAVA 5 проблему решили, используя модификатор volatile. На данный момент решение выглядит так:

Вариант пятый:
Что такое singleton java. Смотреть фото Что такое singleton java. Смотреть картинку Что такое singleton java. Картинка про Что такое singleton java. Фото Что такое singleton java

Не смотря на то, что этот вариант выглядит как идеальное решение, использовать его не рекомендуется т.к. товарищ Allen Holub заметил, что использование volatile модификатора может привести к проблемам производительности на мультипроцессорных системах. Но решать все же вам.

Вот, в общем-то, и все распространенные варианты имплементаций данного паттерна. Но, на этом не заканчиваются подводные камни Singleton-а. Существуют еще несколько моментов, которые нужно учитывать во время проектирования того или иного приложения, использующего Singleton.

Подводные камни
1. Наследование
В подавляющем большинстве случаев в Singleton классах наследование не нужно и, более того, излишне и является следствием over-design. Да и реализация наследования имеет определенные сложности, учитывая, что и сам instance и метод getInstance() статические.
Поэтому, я рекомендую использовать модификатор final и запретить наследование данного класса, если нет особой необходимости в обратном.

2. Две и более виртуальных машины
Каждая виртуальная машина создает свою копию Singleton объекта. И хотя на первый взгляд это выглядит очевидным, во многих распределенных системах, таких как EJB, JINI и RMI все не так просто. Когда промежуточные уровни скрывают (делают прозрачными) распределенные технологи, бывает трудно сказать, где и когда инициализирован объект.
3.Различные Class Loader-ы
Когда 2 class loader-а загружают класс, каждый из них может создать свою копию Singleton-а(в тех случаях, когда instance инициализируется class loader-ом ). Это особенно актуально в использовании сервлетов(servlet), так как в некоторых имплементациях серверов приложений(application server) каждый сервлет имеет свой class loader.

Существует еще ряд проблем, которые менее актуальны (такие как технология reflection и имплементация интерфейсов Cloneable и Serializable), и не будут мною рассмотрены в силу своей экзотичности в сфере применения Singleton классов. Но, в любом случае, с радостью отвечу на любые вопросы к этому материалу.

Вот, в общем-то, и все ключевые моменты, которые я хотел осветить в данной статье. Осталось лишь заметить, что данный материал не претендует быть истиной в последней инстанции и допускает существование точек зрения, отличающихся от точки зрения автора. И даже более того, любые замечания приветствуются и будут прияты к сведению.

Источник

Класс Singleton в Java

В Java класс Singleton (одноэлементный) – это класс, который может иметь только один экземпляр в данный момент времени. Это один из пяти шаблонов Creational Design, который помогает в легкой разработке программ.

Что это такое?

С точки зрения непрофессионала, класс Singleton – это класс, который обеспечивает доступ к нему через один экземпляр за раз. Этот шаблон проектирования предназначен для ограничения ненужного создания экземпляра класса и обеспечения того, чтобы в любой момент времени для каждого экземпляра JVM существовал только один объект класса.

Таким образом, с этим шаблоном любой класс, определенный как Singleton, имеет только один экземпляр с глобальной точкой доступа к нему. В отличие от обычных классов, одноэлементный класс не уничтожается до конца жизненного цикла приложения.

Зачем нужен?

Ограничивая создание экземпляра класса, он экономит место в памяти, поскольку теперь объект не будет создаваться каждый раз, когда делается новый запрос. Вместо этого один объект будет использоваться повторно. По этой причине шаблон Singleton в основном используется с многопоточными приложениями и приложениями баз данных. Он в основном используется для регистрации, кэширования, объединения потоков, настроек конфигурации и многого другого.

Способы создания

Для того чтобы создать класс, вам понадобятся следующие три вещи:

1. Стремительный метод инициализации

Это самый простой метод создания класса Singleton, в котором экземпляр создается во время загрузки класса. Чтобы создать одноэлементный класс с использованием этого метода, вам необходимо выполнить следующие шаги:

Теперь посмотрим, как это реализовать.

Вы можете заметить, что каждый раз, когда мы создаем экземпляр объекта, мы используем метод getInstance(), а не вызываем конструктор класса. Но у него есть свои недостатки.

Если вы используете метод getInstance() для создания Singleton класса, то экземпляр будет создан независимо от того, использует ли его приложение или нет.

2. Ленивый метод инициализации

Этот метод называется отложенной инициализацией, потому что он откладывает создание экземпляра класса до его первого использования. С помощью этого метода объект создается, только если он необходим.

Это помогает избежать ненужного создания экземпляра класса. Чтобы спроектировать одноэлементный класс таким образом, вам необходимо выполнить следующие шаги:

Ниже код показывает, как это сделать.

3. Потокобезопасный метод Singleton

Но вышеупомянутый подход может вызвать некоторые проблемы в параллельных сценариях. Поскольку одноэлементный шаблон в основном используется с многопоточностью, и если несколько потоков входят в условие if одновременно, это может вызвать проблемы.

Чтобы избежать этого, мы пытаемся создать потокобезопасный одноэлементный класс, синхронизируя метод глобального доступа. Это гарантирует, что только один поток выполняет этот метод в любой момент времени. Обратитесь к приведенному ниже коду, чтобы увидеть реализацию:

Но иногда этот подход также может стать очень громоздким, так как каждый раз, когда вызывается метод, ему нужно ждать, пока блокировка не будет снята, прежде чем метод сможет его использовать. Это приводит к замедлению процесса.

4. Ленивая инициализация с методом двойной блокировки

При таком подходе мы не синхронизируем методы. Скорее мы заключаем код создания объекта в синхронизированный блок. Вы можете сказать, что, предварительно проверив блокировки потоков, это уменьшает количество блокировок.

Такой подход обычно приводит к повышению производительности приложения. Проверьте код ниже, чтобы увидеть, как это делается.

5. Метод отложенной загрузки

Этот метод основан на JSL (спецификация языка Java) и в соответствии с этим JVM будет загружать статические элементы данных только тогда, когда они требуются. Таким образом, когда ваш синглтон-класс загружается в JVM, экземпляр не создается.

Кроме того, во время выполнения программы глобальный метод вызывается в последовательном порядке.

С этим методом вам не нужно явно синхронизировать статический getInstance() для загрузки и инициализации. Статический член класса будет вызываться надлежащим образом, остальные параллельные вызовы глобального метода возвращаются в том же порядке без необходимости выполнения накладных расходов на синхронизацию.

Ниже приведен код для выполнения того же.

6. Метод инициализации статического блока

Этот метод создания одноэлементного класса в Java похож на метод активной инициализации. Единственное отличие состоит в том, что экземпляр для этого класса создается в статическом блоке с функцией обработки исключений.

Источник

Singleton (Одиночка) или статический класс?

Статья будет полезна в первую очередь разработчикам, которые теряются на собеседованиях когда слышат вопрос «Назовите основные отличия синглтона от статического класса, и когда следует использовать один, а когда другой?». И безусловно будет полезна для тех разработчиков, которые при слове «паттерн» впадают в уныние или просят прекратить выражаться 🙂

Что такое статический класс?

Что такое Singleton (Одиночка)?

Один из порождающих паттернов, впервые описанный «бандой четырех» (GoF). Гарантирует, что у класса есть только один экземпляр, и предоставляет к нему глобальную точку доступа. Мы не будем подробно рассматривать здесь этот паттерн, его предназначение и решаемые им задачи — в сети существует масса подробной информации о нем (например здесь и здесь). Отмечу лишь что синглтоны бывают потокобезопасные и нет, с простой и отложенной инициализацией.

А если нет разницы — зачем плодить больше?

Так в чем же все-таки разница между этими двумя сущностями и когда следует их использовать? Думаю что лучше всего это проиллюстрировать в следующей таблице:

SingletonStatic class
Количество точек доступаОдна (и только одна) точка доступа — статическое поле InstanceN (зависит от количества публичных членов класса и методов)
Наследование классов Возможно, но не всегда (об этом — ниже)Невозможно — статические классы не могут быть экземплярными, поскольку нельзя создавать экземпляры объекты статических классов
Наследование интерфейсовВозможно, безо всяких ограниченийНевозможно по той же причине, по которой невозможно наследование классов
Возможность передачи в качестве параметровВозможно, поскольку Singleton предоставляет реальный объектОтсутствует
Контроль времени жизни объектаВозможно — например, отложенная инициализация (или создание по требованию)Невозможно по той же причине, по которой невозможно наследование классов
Использование абстрактной фабрики для создания экземпляра классаВозможноНевозможно по причине осутствия самой возможности создания экземпляра
СериализацияВозможноНеприменима по причине отсутствия экземпляра

Рассмотрим подробнее перечисленные выше критерии.

Количество точек доступа

Конечно же имеются ввиду внешние точки доступа, другими словами — публичный контракт взаимодействия класса и его клиентов. Это удобнее проиллюстрировать с помощью кода:

Singleton в «канонической» реализации:

Наследование классов

С наследованием статических классов все просто — оно просто не поддерживается на уровне языка. С Singleton все несколько сложнее. Для удобства использования многие разработчики чаще всего используют следующую реализацию паттерна:

А поскольку множественное наследование в C# и в любом CLI-совместимом языке запрещено — это означает что мы не сможем унаследовать класс Session от любого другого полезного класса. Выходом является делагирование синглтону управления доступом к экземпляру объекта:

Наследование интерфейсов

Использование интерфейсов позволяет достичь большей гибкости, увеличить количество повторно используемого кода, повысить тестируемость, и, самое главное — избежать сильной связности объектов. Статические классы не поддерживают наследования в принципе. Синглтон, напротив, наследование интерфейсов поддерживает в полной мере, поскольку это обычный класс. Но вот использовать эту возможность стоит только в том случае, если экземпляр синглтона планируется передавать в качестве входных параметров в смешанных сценариях или транслировать за границу домена. Пример смешанного сценария:

Возможность передачи в качестве параметров

Для статических классов это не поддерживается — можно передать разве что тип, но в большинстве ситуаций это бесполезно, за исключением случаев применения механизмов отражения (reflection). Синглтон же по сути является обычным экземпляром объекта:

Контроль времени жизни объекта

Время жизни статического класса ограничено временем жизни домена — если мы создали этот домен вручную, то мы косвенно управляем временем жизни всех его статических типов. Временем жизни синглтона мы можем управлять по нашему желанию. Яркий пример — отложенная инициализация:

Можно также добавить операцию удаления экземпляра синглтона:

Данная операция является крайне небезопасной, поскольку синглтон может хранить некоторое состояние и поэтому его пересоздание может иметь нежелательные последствия для его клиентов. Если все же необходимость в таком методе возникла (что скорее всего указывает на ошибки проектирования) то нужно постараться свести к минимуму возможное зло от его использования — например сделать его закрытым и вызывать внутри свойства Instance при определенных условиях:

Использование абстрактной фабрики для создания экземпляра класса

Статический класс не поддерживает данной возможности ввиду того, что нельзя создать экземпляр статического класса. В случае с синглтоном все выглядит просто:

Правда в варианте с аггрегацией синглтона придеться применить не совсем красивое и, немного громоздкое решение:

Сериализация

Сериализация применима только к экземплярам классов. Статический класс не может иметь экзмпляров поэтому сериализовать в данном случае нечего.

Так что же использовать Синглтон или Статический класс?

В любом случае выбор решения зависит от разработчика и от специфики решаемой им задачи. Но, в любом случае, можно сделать следующие выводы:

Источник

Паттерны проектирования: Singleton

Что такое синглтон?

Дает гарантию, что у класса будет всего один экземпляр класса.

Предоставляет глобальную точку доступа к экземпляру данного класса.

Приватный конструктор. Ограничивает возможность создания объектов класса за пределами самого класса.

Варианты реализации

Ленивая инициализация: когда класс загружается во время работы приложения именно тогда, когда он нужен.

Простота и прозрачность кода: метрика, конечно, субъективная, но важная.

Потокобезопасность: корректная работа в многопоточной среде.

Высокая производительность в многопоточной среде: потоки блокируют друг друга минимально, либо вообще не блокируют при совместном доступе к ресурсу.

Не ленивая инициализация: когда класс загружается при старте приложения, независимо от того, нужен он или нет (парадокс, в мире IT лучше быть лентяем)

Сложность и плохая читаемость кода. Метрика также субъективная. Будем считать, что если кровь пошла из глаз, реализация так себе.

Отсутствие потокобезопасности. Иными словами, “потокоопасность”. Некорректная работа в многопоточной среде.

Низкая производительность в многопоточной среде: потоки блокируют друг друга все время либо часто, при совместном доступе к ресурсу.

Самая простая реализация. Плюсы:

    Простота и прозрачность кода

    Высокая производительность в многопоточной среде

    Реализация интересна. Мы можем инициализироваться лениво, но утратили потокобезопасность. Не беда: в реализации номер три мы все синхронизируем.

    Низкая производительность в многопоточной среде

    Отлично! В реализации номер три мы вернули потокобезопасность! Правда, медленную… Теперь метод getInstance синхронизирован, и входить в него можно только по одному. На самом деле нам нужно синхронизировать не весь метод, а лишь ту его часть, в которой мы инициализируем новый объект класса. Но мы не можем просто обернуть в synchronized блок часть, отвечающую за создание нового объекта: это не обеспечит потокобезопасность. Все немного сложнее. Правильный способ синхронизации представлен ниже:

    Double Checked Locking

    Высокая производительность в многопоточной среде

    Не поддерживается на версиях Java ниже 1.5 (в версии 1.5 исправили работу ключевого слова volatile)

    Class Holder Singleton

    Высокая производительность в многопоточной среде.

    Реализация практически идеальная. И ленивая, и потокобезопасная, и быстрая. Но есть нюанс, описанный в минусе. Сравнительная таблица различных реализаций паттерна Singleton:

    РеализацияЛенивая инициализацияПотокобезопасностьСкорость работы при многопоточностиКогда использовать?
    Simple Solution+БыстроНикогда. Либо когда не важна ленивая инициализация. Но лучше никогда.
    Lazy Initialization+НеприменимоВсегда, когда не нужна многопоточность
    Synchronized Accessor++МедленноНикогда. Либо когда скорость работы при многопоточности не имеет значения. Но лучше никогда
    Double Checked Locking++БыстроВ редких случаях, когда нужно обрабатывать исключения при создании синглтона. (когда неприменим Class Holder Singleton)
    Class Holder Singleton++БыстроВсегда, когда нужна многопоточность и есть гарантия, что объект синглтон класса будет создан без проблем.

    Плюсы и минусы паттерна Singleton

    Дает гарантию, что у класса будет всего один экземпляр класса.

    Предоставляет глобальную точку доступа к экземпляру данного класса.

    Синглтон нарушает SRP (Single Responsibility Principle) — класс синглтона, помимо непосредственных обязанностей, занимается еще и контролированием количества своих экземпляров.

    Зависимость обычного класса или метода от синглтона не видна в публичном контракте класса.

    Глобальные переменные это плохо. Синглтон превращается в итоге в одну здоровенную глобальную переменную.

    Наличие синглтона снижает тестируемость приложения в целом и классов, которые используют синглтон, в частности.

    Ну вот и все. Мы рассмотрели с тобой паттерн проектирования синглтон. Теперь в разговоре за жизнь с друзьями программистами ты сможешь сказать не только чем он хорош, но и пару слов о том, чем он плох. Удачи в освоении новых знаний.

    Источник

    Добавить комментарий

    Ваш адрес email не будет опубликован. Обязательные поля помечены *