Автор оригинала: 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 .
Я надеюсь, что эта статья поможет вам разобраться в тонких деталях шаблона одноэлементного дизайна, дайте мне знать через ваши мысли и комментарии.