О паттерне singleton в моем блоге много статей. Будучи наиболее часто используемым шаблоном проектирования среди 23 шаблонов проектирования, шаблон singleton не так прост, как можно себе представить. Потому что при разработке синглтонов необходимо учитывать множество вопросов, таких как вопросы безопасности потоков, повреждение сериализации синглтонов и т. д.
Список статей, связанных с синглтоном:
Шаблоны проектирования (2) — шаблон Singleton
Шаблоны проектирования (3) — эти синглтоны в JDK
Семь способов написать шаблон singleton
Эти вещи о синглтонах и сериализации
Как реализовать потокобезопасный синглтон без использования synchronized и блокировки?
Как реализовать потокобезопасный синглтон без использования synchronized и блокировки? (два)
Если вы мало что знаете о синглтонах или не очень хорошо разбираетесь в проблемах безопасности потоков, связанных с синглтонами, а сериализация уничтожит синглтоны, вы можете сначала прочитать приведенные выше статьи. Я полагаю, что после прочтения вышеупомянутых шести статей вы получите более глубокое представление о шаблоне singleton.
Мы знаем, что обычно существует семь способов записи одноэлементного шаблона, так какой же из этих семи способов записи является лучшим? Зачем? В этой статье мы рассмотрим это.
Как лучше всего написать синглтон
В StakcOverflow естьWhat is an efficient way to implement a singleton pattern in Java?обсуждение:
Как показано выше, ответ с наибольшим количеством голосов: использовать перечисление.
Ответчик процитировал точку зрения Джошуа Блоха, четко выраженную в «Эффективной Java»:
Метод реализации синглетонов с помощью перечислений не получил широкого распространения, но одноэлементные типы перечисления стали лучшим способом реализации синглтонов.
Если вы действительно понимаете использование синглтона и некоторых возможных ям, то можете прийти к такому же выводу, а именно: использование перечисления для реализации синглтона — хороший способ.
Написание одноэлементного перечисления просто
Если вы видели "Семь способов написать шаблон singleton«В коде всех способов реализации синглтона вы обнаружите, что код для реализации синглтона различными способами более сложен. Основная причина заключается в рассмотрении вопросов безопасности потоков.
Давайте кратко сравним метод «двойной проверки блокировки» и метод перечисления для реализации одноэлементного кода.
«Блокировка с двойной проверкой» реализует синглтон:
public class Singleton {
private volatile static Singleton singleton;
private Singleton (){}
public static Singleton getSingleton() {
if (singleton == null) {
synchronized (Singleton.class) {
if (singleton == null) {
singleton = new Singleton();
}
}
}
return singleton;
}
}
Перечисление реализует синглтон:
public enum Singleton {
INSTANCE;
public void whateverMethod() {
}
}
Напротив, вы обнаружите, что код для перечислений для реализации синглетонов намного проще.
Приведенный выше код для проверки двойной блокировки раздут, поскольку большая часть кода является потокобезопасной. Чтобы найти компромисс между обеспечением безопасности потоков и степенью детализации блокировки, код неизбежно будет усложняться. Однако этот код по-прежнему содержит ошибки, потому что он не решает проблемы, связанной с тем, что десериализация нарушит работу синглтона.
Перечисления решают проблемы безопасности потоков
упомянутый выше. Чтобы реализовать синглтон без перечисления, вы должны самостоятельно обеспечить безопасность потоков, поэтому другие методы должны быть раздуты. Так почему же использование перечислений не должно решать проблемы безопасности потоков?
На самом деле дело не в том, что использование перечисления не должно гарантировать потокобезопасность, а в том, что гарантия потокобезопасности не требует от нас внимания. Иными словами, на самом деле потокобезопасность по-прежнему гарантируется на «нижнем уровне».
Итак, что именно означает «дно»?
Речь идет о реализации перечисления. Эта часть может относиться к моему другому сообщению в блогеУглубленный анализ типа перечисления Java — безопасность потоков и сериализация перечисления., здесь я кратко поясняю:
При определении перечисления enum является ключевым словом в Java, как и класс. Подобно тому, как класс применяет класс к перечислению, перечисление также соответствует перечислению.
Поместив определенное перечислениедекомпилировать, мы можем обнаружить, что на самом деле перебор проходитjavac
После компиляции он будет преобразован во что-то вродеpublic final class T extends Enum
Определение.
При этом каждый элемент перечисления в перечислении проходит черезstatic
определять. как:
public enum T {
SPRING,SUMMER,AUTUMN,WINTER;
}
Декомпилированный код:
public final class T extends Enum
{
//省略部分内容
public static final T SPRING;
public static final T SUMMER;
public static final T AUTUMN;
public static final T WINTER;
private static final T ENUM$VALUES[];
static
{
SPRING = new T("SPRING", 0);
SUMMER = new T("SUMMER", 1);
AUTUMN = new T("AUTUMN", 2);
WINTER = new T("WINTER", 3);
ENUM$VALUES = (new T[] {
SPRING, SUMMER, AUTUMN, WINTER
});
}
}
Друзья, которые разбираются в механизме загрузки классов JVM, должны понимать эту часть.static
Свойства типа инициализируются после загрузки класса, мы находимся вУглубленный анализ механизма Java ClassLoader (уровень исходного кода)иЗагрузка, связывание и инициализация классов JavaКак показано в двух статьях, когда класс Java фактически используется впервые, инициализируются статические ресурсы, а процесс загрузки и инициализации классов Java является потокобезопасным (поскольку виртуальная машина загружает перечисленные классы, когда используйте метод loadClass ClassLoader, который использует синхронизированные блоки кода для обеспечения безопасности потоков). Таким образом, создание типа перечисления является потокобезопасным.
Другими словами, перечисление, которое мы определяем, будет загружено и инициализировано виртуальной машиной, когда оно фактически используется в первый раз, и этот процесс инициализации является потокобезопасным. И мы знаем, что для решения проблемы параллелизма синглтона основным решением является решение проблемы безопасности потоков в процессе инициализации.
Таким образом, благодаря указанным выше свойствам перечислений, синглтоны, реализованные перечислениями, по своей сути потокобезопасны.
Перечисления решают проблему десериализации, нарушающей синглтоны
Как мы упоминали ранее, на самом деле существует определенная проблема с синглтоном, реализованным с помощью блокировки двойной проверки, то есть этот синглтон может быть уничтожен блокировкой сериализации, об этом уничтожении и решениях см.Эти вещи о синглтонах и сериализации, который здесь не будет подробно описываться.
Итак, для сериализации, почему перечисление имеет неотъемлемое преимущество? Ответ можно найти вJava Object Serialization SpecificationНайдите ответ в . Сериализация перечисления специально оговаривается следующим образом:
Общий смысл таков: при сериализации Java выводит в результат только атрибут name объекта перечисления, а при десериализации передаетjava.lang.Enum
изvalueOf
метод для поиска объектов перечисления по имени. В то же время компилятор не позволяет настраивать этот механизм сериализации, поэтому он отключен.writeObject
,readObject
,readObjectNoData
,writeReplace
иreadResolve
и другие методы.
В процессе десериализации обычных классов Java конструктор класса по умолчанию вызывается отражением для инициализации объекта. Таким образом, даже если конструктор в синглтоне является закрытым, он будет уничтожен при отражении. Поскольку десериализованный объект обновляется, это нарушает синглтон.
Однако десериализация перечислений не достигается посредством отражения. Следовательно, проблема уничтожения синглтона, вызванная десериализацией, не возникнет. Эта часть находится вУглубленный анализ типа перечисления Java — безопасность потоков и сериализация перечисления.Есть и более подробные вступления, и показаны некоторые коды.Заинтересованные друзья могут пойти почитать.
Суммировать
Среди всех одноэлементных реализаций перечисление — самый простой способ написания кода Причина, по которой код очень лаконичен, заключается в том, что Java предоставляет намenum
ключевое слово, мы можем легко объявить тип перечисления, не беспокоясь о безопасности потоков во время его инициализации, потому что класс перечисления будет инициализирован с безопасностью потоков при его загрузке виртуальной машиной.
Кроме того, в плане сериализации в Java есть четкие регламенты, а сериализация и десериализация перечислений специально настроены. Это может избежать проблемы уничтожения синглтона, вызванной отражением во время десериализации.