Java как создать singleton класс

Класс Singleton в Java

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

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

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

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

Зачем нужен?

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

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

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

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

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

  1. Объявите конструктор частным.
  2. Следующим шагом является создание закрытого члена класса для этого класса Singleton.
  3. Теперь вам нужно определить фабричный метод, который будет использоваться для возврата объекта вашего класса, который мы создали как экземпляр члена класса.
  4. Вы даже можете объявить статический член public, если хотите получить прямой доступ к этому статическому экземпляру.
Читайте также:  Css box sizing width

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

//Eager Initialization public class EagerSingleton < private static final EagerSingleton INSTANCE = new EagerSingleton(); private EagerSingleton() <>public static EagerSingleton getInstance() < return INSTANCE; >>

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

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

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

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

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

  1. Прежде всего, объявите конструктор как частный.
  2. Затем вам нужно создать частный статический экземпляр для этого класса, но пока не нужно его создавать.
  3. Наконец, создайте метод фабрики, который сначала проверит, является ли элемент экземпляра нулевым или нет. Если нет, то он создаст для вас экземпляр класса singleton и вернет его.

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

//Lazy Initialization public class LazySingleton < private static LazySingleton INSTANCE = null; private LazySingleton() <>public static LazySingleton getInstance() < if (INSTANCE == null) < synchronized(LazySingleton.class) < INSTANCE = new LazySingleton(); >> return INSTANCE; > >

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

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

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

//Thread Safe Singleton public class ThreadSafeSingleton < private static ThreadSafeSingleton INSTANCE; private ThreadSafeSingleton()<>public static synchronized ThreadSafeSingleton getInstance() < if(INSTANCE == null)< INSTANCE = new ThreadSafeSingleton(); >return INSTANCE; > >

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

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

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

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

//Lazy Initialization with Double Lock public class LazyDoubleLockSingleton < private static LazyDoubleLockSingleton INSTANCE = null; private LazyDoubleLockSingleton() <>public static LazyDoubleLockSingleton getInstance() < if (INSTANCE == null) < synchronized (LazyDoubleLockSingleton.class) < if (INSTANCE == null) < INSTANCE = new LazyDoubleLockSingleton(); >> > return INSTANCE; > >

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

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

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

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

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

//Lazy Load Method public class LazyLoadSingleton < private LazyLoadSingleton() <>private static class SingletonClassHolder < static final Var INSTANCE = new LazyLoadSingleton(); >public static LazyLoadSingleton getInstance() < return SingletonClassHolder.INSTANCE; >>

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

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

//Static Block Initialization public class StaticBlockSingleton < private static StaticBlockSingleton INSTANCE; private StaticBlockSingleton()<>//exception handling within static block static < try< INSTANCE = new StaticBlockSingleton(); >catch (Exception e) < throw new RuntimeException("Exception occured while creating a Singleton Class"); >> public static StaticBlockSingleton getInstance() < return INSTANCE; >>

Источник

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

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

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

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

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

1 Static field

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

2 Enum Singleton

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

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

Ленивый Singleton в Java

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

1 Synchronized Accessor
public class Singleton < private static Singleton instance; public static synchronized Singleton getInstance() < if (instance == null) < instance = new Singleton(); >return instance; > > 

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

2 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; > > 

+ Ленивая инициализация
+ Высокая производительность
Поддерживается только с 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
public class Singleton < public static class SingletonHolder < public static final Singleton HOLDER_INSTANCE = new Singleton(); >public static Singleton getInstance() < return SingletonHolder.HOLDER_INSTANCE; >> 

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

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 классы?” 🙂

Источник

Оцените статью