Введение в шаблон Singleton
Шаблон singleton — один из самых простых, основных и наиболее часто используемых шаблонов проектирования в Java. Во время выполнения гарантируется, что создается только один экземпляр класса, существует только один экземпляр класса и предоставляется глобальная точка доступа к нему. Давайте поговорим о N способах реализации одноэлементного шаблона в Java.
голодный китаец
public class Singleton {
private static Singleton instance = new Singleton();
private Singleton() {
}
public static Singleton getInstance() {
return instance;
}
}
Это самый простой и грубый способ реализовать безопасный одноэлементный шаблон, который мы называем голодным. Причина, по которой это называется «Голодный китайский стиль», заключается в том, что я очень голоден, я хочу есть немедленно и не хочу ждать времени производства. При таком способе записи экземпляр Singleton создается при загрузке класса.
Недостаток голодного китайского стиля в том, что экземпляр может быть создан, когда экземпляр не нужен, что не имеет эффекта ленивой загрузки. Преимущество в том, что он прост в реализации, безопасен и надежен.
ленивый
public class Singleton {
private static Singleton instance;
private Singleton() {
}
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
По сравнению с голодным стилем, ленивый стиль менее «голоден» и создает экземпляр, когда он действительно нужен. В методе getInstance кажется идеальным сначала определить, пуст ли экземпляр, а затем решить, создавать ли экземпляр, но возникает проблема безопасности потоков. При одновременном получении экземпляров могут возникнуть ситуации, когда создается несколько экземпляров. Поэтому этот код нуждается в доработке.
public class SingletonSafe {
private static volatile SingletonSafe singleton;
private SingletonSafe() {
}
public static SingletonSafe getSingleton() {
if (singleton == null) {
synchronized (SingletonSafe.class) {
if (singleton == null) {
singleton = new SingletonSafe();
}
}
}
return singleton;
}
}
используется здесьдвойная проверкаметод, потокобезопасная обработка была выполнена по ленивому одноэлементному шаблону. С помощью блокировки можно гарантировать, что только один поток одновременно переходит ко второму нулевому коду, что гарантирует создание только одного экземпляра. Ключевое слово volatile также используется здесь для модификации синглтона, и его наиболее важной ролью является предотвращение перестановки инструкций.
статический внутренний класс
public class Singleton {
private static class SingletonHolder {
private static Singleton instance = new Singleton();
}
private Singleton() {
}
public static Singleton getInstance() {
return SingletonHolder.instance;
}
}
Поточно-ориентированный режим реализуется с помощью статических внутренних классов, при этом статический внутренний класс не будет загружаться при загрузке класса Singleton, а будет загружаться при вызове метода getInstance(), который достигает эффекта ленивой загрузки.
Кажется, что статические внутренние классы кажутся самым совершенным методом, но это не так, могут быть атаки отражения или атаки десериализации. И посмотрите на следующий код:
public static void main(String[] args) throws Exception {
Singleton singleton = Singleton.getInstance();
Constructor<Singleton> constructor = Singleton.class.getDeclaredConstructor();
constructor.setAccessible(true);
Singleton newSingleton = constructor.newInstance();
System.out.println(singleton == newSingleton);
}
результат операции:
Судя по результатам, эти два экземпляра не совпадают, что нарушает принцип одноэлементного шаблона.
В дополнение к атакам отражения также могут быть случаи атак десериализации. следующее:
Импорт зависимостей:
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.8.1</version>
</dependency>
Эта зависимость предоставляет служебные классы сериализации и десериализации.
Класс Singleton реализует интерфейс java.io.Serializable.
следующее:
public class Singleton implements Serializable {
private static class SingletonHolder {
private static Singleton instance = new Singleton();
}
private Singleton() {
}
public static Singleton getInstance() {
return SingletonHolder.instance;
}
public static void main(String[] args) {
Singleton instance = Singleton.getInstance();
byte[] serialize = SerializationUtils.serialize(instance);
Singleton newInstance = SerializationUtils.deserialize(serialize);
System.out.println(instance == newInstance);
}
}
результат операции:
Шаблон Singleton через перечисление
В эффективной Java (книга действительно хороша) говорится, что лучшим шаблоном реализации синглтона является шаблон enum. Используя характеристики перечисления, позвольте JVM помочь нам обеспечить безопасность потоков и проблемы с одним экземпляром. Кроме того, метод записи очень прост.
public enum Singleton {
INSTANCE;
public void doSomething() {
System.out.println("doSomething");
}
}
Вызовите метод:
public class Main {
public static void main(String[] args) {
Singleton.INSTANCE.doSomething();
}
}
Его можно вызвать напрямую через Singleton.INSTANCE.doSomething(). Удобно, лаконично и безопасно.
Суммировать
Выше перечислены различные одноэлементные шаблоны и проанализированы их преимущества и недостатки. В то же время он также вводит текущий лучший метод записи синглтона - режим перечисления.Я считаю, что в будущем метод записи синглтона режима перечисления будет становиться все более и более популярным.