что такое 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.

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

Client Server
Synchronized accessor 42,6 86,3
Double Checked Lock & volatile 179,8 202,4
On Demand Holder 181,6 202,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?

Что такое Singleton можно объяснить одной фразой:

Итак, представьте, что в программе Вам нужно:

Именно такой класс называется Singleton-ом.

Чем Singleton НЕ ЯВЛЯЕТСЯ?

Может ли меняться Singleton?

Да, состояние Singleton-а может быть как изменяемым, так и не изменяемым.

А теперь представим, что именно Вам нужно реализовать систему защиты от таких вот Вась на своем сервере.

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

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

В такой ситуации нам как раз и поможет Singleton!

Читайте также:  калий в чем содержиться

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

Так, чтобы решить эту проблему, нам понадобился изменяемый Singleton.

Одним из примеров неизменяемого Singleton-а может быть класс, который ставит водяной знак (watermark) на изображения, чтобы предотвратить их кражу.

Несмотря на то, что паттерн Singleton может показаться легким в теории, на практике можно наткнуться на «подводные камни». Давайте посмотрим на примерах.

Как и когда создавать Singleton?

Тем не менее, обратите внимание на то, какая видимость должна быть у Вашего Singleton-а.

В зависимости от того, работаете Вы с потоками или нет, можно будет выбрать оптимальную реализацию 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 в JAVA

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

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

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

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

Вариант первый – самый простой, который приходит в голову сразу после понимания проблемы.

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

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

Вариант второй:

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

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

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

Вариант третий:
“Initialization on Demand Holder”

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

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

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

Вариант четвертый:

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

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

Наиболее распространенный способ — «Double-Checked Locking». В своем оригинальном варианте:

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

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

Вариант пятый:

Не смотря на то, что этот вариант выглядит как идеальное решение, использовать его не рекомендуется т.к. товарищ 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 классов. Но, в любом случае, с радостью отвечу на любые вопросы к этому материалу.

Читайте также:  что такое continuous delivery

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

Источник

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Источник

Строительный портал