Автор оригинала: Pankaj Kumar.
Шаблон Java Singleton является одним из Групп из четырех шаблонов проектирования и входит в категорию Шаблон креативного дизайна .
Из определения следует, что это очень простой шаблон проектирования, но когда дело доходит до реализации, он сопряжен с множеством проблем реализации.
Реализация одноэлементного шаблона Java всегда была спорной темой среди разработчиков. Здесь мы узнаем о принципах одноэлементного шаблона проектирования, различных способах реализации одноэлементного шаблона проектирования и некоторых передовых методах его использования.
Одноэлементный Шаблон
- Одноэлементный шаблон ограничивает создание экземпляра класса и гарантирует, что в виртуальной машине java существует только один экземпляр класса.
- Одноэлементный класс должен предоставить глобальную точку доступа для получения экземпляра класса.
- Одноэлементный шаблон используется для ведения журнала , объектов драйверов, кэширования и пула потоков .
- Шаблон одноэлементного дизайна также используется в других шаблонах дизайна, таких как Абстрактная фабрика , Конструктор , Прототип , Фасад и т.д.
- Шаблон одноэлементного проектирования также используется в основных классах java, например
java.lang.Runtime,java.awt.Desktop.
Реализация Одноэлементного Шаблона Java
Для реализации одноэлементного шаблона у нас есть разные подходы, но все они имеют следующие общие концепции.
- Частный конструктор для ограничения создания экземпляра класса из других классов.
- Частная статическая переменная того же класса, которая является единственным экземпляром класса.
- Общедоступный статический метод, возвращающий экземпляр класса, является глобальной точкой доступа для внешнего мира, чтобы получить экземпляр одноэлементного класса.
В дальнейших разделах мы изучим различные подходы к реализации одноэлементного шаблона и проблемы проектирования, связанные с реализацией.
- Нетерпеливая инициализация
- Инициализация статического блока
- Ленивая Инициализация
- Потокобезопасный Синглтон
- Реализация Билла Пью Синглтона
- Использование отражения для уничтожения одноэлементного шаблона
- Перечисление Синглтона
- Сериализация и Синглтон
1. Нетерпеливая инициализация
При активной инициализации экземпляр одноэлементного класса создается во время загрузки класса, это самый простой способ создания одноэлементного класса, но у него есть недостаток, заключающийся в том, что экземпляр создается, даже если клиентское приложение может его не использовать.
Вот реализация одноэлементного класса статической инициализации.
package com.journaldev.singleton;
public class EagerInitializedSingleton {
private static final EagerInitializedSingleton instance = new EagerInitializedSingleton();
//private constructor to avoid client applications to use constructor
private EagerInitializedSingleton(){}
public static EagerInitializedSingleton getInstance(){
return instance;
}
}
Если ваш одноэлементный класс не использует много ресурсов, следует использовать именно этот подход. Но в большинстве сценариев одноэлементные классы создаются для таких ресурсов, как Файловая система, подключения к базе данных и т.д. Мы должны избегать создания экземпляра до тех пор, пока клиент не вызовет метод getInstance . Кроме того, этот метод не предоставляет никаких опций для обработки исключений.
2. Инициализация статического блока
Реализация инициализации статического блока аналогична инициализации в режиме ожидания, за исключением того, что экземпляр класса создается в статическом блоке, который предоставляет возможность обработки исключений .
package com.journaldev.singleton;
public class StaticBlockSingleton {
private static StaticBlockSingleton instance;
private StaticBlockSingleton(){}
//static block initialization for exception handling
static{
try{
instance = new StaticBlockSingleton();
}catch(Exception e){
throw new RuntimeException("Exception occured in creating singleton instance");
}
}
public static StaticBlockSingleton getInstance(){
return instance;
}
}
Как активная инициализация, так и инициализация статического блока создают экземпляр еще до его использования, и это не лучшая практика для использования. Поэтому в дальнейших разделах мы узнаем, как создать одноэлементный класс, поддерживающий ленивую инициализацию.
Читать : Статическая Java
3. Ленивая Инициализация
Метод ленивой инициализации для реализации одноэлементного шаблона создает экземпляр в методе глобального доступа. Вот пример кода для создания одноэлементного класса с помощью этого подхода.
package com.journaldev.singleton;
public class LazyInitializedSingleton {
private static LazyInitializedSingleton instance;
private LazyInitializedSingleton(){}
public static LazyInitializedSingleton getInstance(){
if(instance == null){
instance = new LazyInitializedSingleton();
}
return instance;
}
}
Приведенная выше реализация отлично работает в случае однопоточной среды, но когда дело доходит до многопоточных систем, это может вызвать проблемы, если несколько потоков одновременно находятся в состоянии if. Это уничтожит шаблон одноэлементного элемента, и оба потока получат разные экземпляры класса одноэлементного элемента. В следующем разделе мы рассмотрим различные способы создания потокобезопасного одноэлементного класса.
4. Потокобезопасный Синглтон
Самый простой способ создать потокобезопасный одноэлементный класс-синхронизировать метод глобального доступа , чтобы только один поток мог выполнять этот метод одновременно. Общая реализация этого подхода аналогична приведенному ниже классу.
package com.journaldev.singleton;
public class ThreadSafeSingleton {
private static ThreadSafeSingleton instance;
private ThreadSafeSingleton(){}
public static synchronized ThreadSafeSingleton getInstance(){
if(instance == null){
instance = new ThreadSafeSingleton();
}
return instance;
}
}
Вышеописанная реализация отлично работает и обеспечивает потокобезопасность, но снижает производительность из-за затрат, связанных с синхронизированным методом, хотя она нужна нам только для первых нескольких потоков, которые могут создавать отдельные экземпляры (Читайте: Синхронизация Java ). Чтобы избежать этих дополнительных накладных расходов каждый раз, используется принцип двойной проверки блокировки . При таком подходе синхронизированный блок используется внутри условия if с дополнительной проверкой, чтобы убедиться, что создан только один экземпляр одноэлементного класса.
Следующий фрагмент кода обеспечивает реализацию блокировки с двойной проверкой.
public static ThreadSafeSingleton getInstanceUsingDoubleLocking(){
if(instance == null){
synchronized (ThreadSafeSingleton.class) {
if(instance == null){
instance = new ThreadSafeSingleton();
}
}
}
return instance;
}
Прочитайте : Потокобезопасный Одноэлементный класс
5. Реализация Билла Пью Синглтона
До Java 5 в модели памяти java было много проблем, и описанные выше подходы приводили к сбоям в определенных сценариях, когда слишком много потоков пытались получить экземпляр одноэлементного класса одновременно. Поэтому Билл Пью предложил другой подход к созданию одноэлементного класса с использованием внутреннего статического вспомогательного класса . Реализация Билла Пью Синглтона выглядит следующим образом;
package com.journaldev.singleton;
public class BillPughSingleton {
private BillPughSingleton(){}
private static class SingletonHelper{
private static final BillPughSingleton INSTANCE = new BillPughSingleton();
}
public static BillPughSingleton getInstance(){
return SingletonHelper.INSTANCE;
}
}
Обратите внимание на закрытый внутренний статический класс , содержащий экземпляр одноэлементного класса. При загрузке одноэлементного класса Одноэлементный вспомогательный класс не загружается в память, и только когда кто-то вызывает метод getInstance , этот класс загружается и создает экземпляр одноэлементного класса.
Это наиболее широко используемый подход для одноэлементного класса, поскольку он не требует синхронизации. Я использую этот подход во многих своих проектах, и его также легко понять и реализовать.
Читать : Вложенные классы Java
6. Использование отражения для уничтожения одноэлементного шаблона
Отражение может быть использовано для уничтожения всех вышеперечисленных подходов к реализации одного элемента. Давайте посмотрим на это на примере класса.
package com.journaldev.singleton;
import java.lang.reflect.Constructor;
public class ReflectionSingletonTest {
public static void main(String[] args) {
EagerInitializedSingleton instanceOne = EagerInitializedSingleton.getInstance();
EagerInitializedSingleton instanceTwo = null;
try {
Constructor[] constructors = EagerInitializedSingleton.class.getDeclaredConstructors();
for (Constructor constructor : constructors) {
//Below code will destroy the singleton pattern
constructor.setAccessible(true);
instanceTwo = (EagerInitializedSingleton) constructor.newInstance();
break;
}
} catch (Exception e) {
e.printStackTrace();
}
System.out.println(instanceOne.hashCode());
System.out.println(instanceTwo.hashCode());
}
}
Когда вы запустите приведенный выше тестовый класс, вы заметите, что хэш-код обоих экземпляров не совпадает, что разрушает шаблон одноэлементного кода. Отражение очень мощно и используется во многих фреймворках, таких как Spring и Hibernate, ознакомьтесь с Учебником по отражению Java .
7. Перечисление Синглтонов
Чтобы преодолеть эту ситуацию с помощью размышлений, Джошуа Блох предлагает использовать перечисление для реализации одноэлементного шаблона проектирования, поскольку Java гарантирует, что любое значение перечисления создается только один раз в программе Java. Поскольку значения перечисления Java доступны во всем мире, то же самое относится и к синглету. Недостатком является то, что тип перечисления несколько негибок; например, он не допускает ленивой инициализации.
package com.journaldev.singleton;
public enum EnumSingleton {
INSTANCE;
public static void doSomething(){
//do something
}
}
Прочитайте : Перечисление Java
8. Сериализация и синглтон
Иногда в распределенных системах нам необходимо реализовать сериализуемый интерфейс в одноэлементном классе, чтобы мы могли сохранить его состояние в файловой системе и получить его позже. Вот небольшой одноэлементный класс, который также реализует сериализуемый интерфейс.
package com.journaldev.singleton;
import java.io.Serializable;
public class SerializedSingleton implements Serializable{
private static final long serialVersionUID = -7604766932017737115L;
private SerializedSingleton(){}
private static class SingletonHelper{
private static final SerializedSingleton instance = new SerializedSingleton();
}
public static SerializedSingleton getInstance(){
return SingletonHelper.instance;
}
}
Проблема с сериализованным одноэлементным классом заключается в том, что всякий раз, когда мы десериализуем его, он создает новый экземпляр класса. Давайте посмотрим на это с помощью простой программы.
package com.journaldev.singleton;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectInputStream;
import java.io.ObjectOutput;
import java.io.ObjectOutputStream;
public class SingletonSerializedTest {
public static void main(String[] args) throws FileNotFoundException, IOException, ClassNotFoundException {
SerializedSingleton instanceOne = SerializedSingleton.getInstance();
ObjectOutput out = new ObjectOutputStream(new FileOutputStream(
"filename.ser"));
out.writeObject(instanceOne);
out.close();
//deserailize from file to object
ObjectInput in = new ObjectInputStream(new FileInputStream(
"filename.ser"));
SerializedSingleton instanceTwo = (SerializedSingleton) in.readObject();
in.close();
System.out.println("instanceOne hashCode="+instanceOne.hashCode());
System.out.println("instanceTwo hashCode="+instanceTwo.hashCode());
}
}
Результатом приведенной выше программы является;
instanceOne hashCode=2011117821 instanceTwo hashCode=109647522
Таким образом, он разрушает одноэлементный шаблон, для преодоления этого сценария все, что нам нужно сделать, это обеспечить реализацию метода readResolve () .
protected Object readResolve() {
return getInstance();
}
После этого вы заметите, что хэш-код обоих экземпляров в тестовой программе одинаков.
Прочитайте : Сериализация Java и Десериализация Java .
Я надеюсь, что эта статья поможет вам разобраться в тонких деталях шаблона одноэлементного дизайна, дайте мне знать через ваши мысли и комментарии.