«Вы знаете, как пишется слово «фенхель» для бобов укропа?»
Есть ли несколько полезных способов написать запутанный одноэлементный шаблон? Это несколько полезно. На собеседованиях один или несколько из этих методов письма часто выбирают в качестве начала разговора. При изучении шаблонов проектирования и стилей кодирования легко перейти к другим вопросам. Вот несколько наиболее часто используемых способов письма для обезьян, но не пытайтесь запомнить «метод письма бобов укропа».Самая большая радость в программировании“know everything, control everything”
.
Версия JDK: оракул java 1.8.0_102
Его можно условно разделить на 4 категории, а их основные формы, варианты и характеристики описаны ниже.
полный мужской режим
Удовлетворение — это одноэлементный шаблон с наибольшим количеством вариантов. Мы начинаем с богача, и постепенно разбираемся в вопросах, на которые необходимо обратить внимание при реализации паттерна singleton через его варианты.
обычный толстяк
Сытый человек означает, что он сыт, он не спешит лишний раз есть и ест, когда голоден. Таким образом, он не инициализирует синглтон сначала, а затем инициализирует его при первом использовании, то есть“懒加载”
.
// 饱汉
// UnThreadSafe
public class Singleton1 {
private static Singleton1 singleton = null;
private Singleton1() {
}
public static Singleton1 getInstance() {
if (singleton == null) {
singleton = new Singleton1();
}
return singleton;
}
}
Суть режима сатаны — отложенная загрузка. Преимущество в том, что он запускается быстрее и экономит ресурсы, синглтон не нужно инициализировать до первого обращения к экземпляру, небольшой недостаток в том, что его сложно писать, а главный недостаток в том, что поток не безопасно, а оператор if имеет условия гонки.
Беда написать не большая проблема, да и читабельность хорошая. следовательно,В однопоточной среде предпочтительным способом написания является базовый материал.. Но в многопоточной среде основы совершенно недоступны. Следующие варианты — это все попытки решить основную проблему небезопасности потоков.
Полный мужчина - Вариант 1
Самым жестоким нарушением является модификация метода getInstance() ключевым словом synchronized, что может обеспечить абсолютную безопасность потоков.
// 饱汉
// ThreadSafe
public class Singleton1_1 {
private static Singleton1_1 singleton = null;
private Singleton1_1() {
}
public synchronized static Singleton1_1 getInstance() {
if (singleton == null) {
singleton = new Singleton1_1();
}
return singleton;
}
}
Преимущество варианта 1 в том, что он прост в написании и абсолютно потокобезопасен; недостаток в том, что производительность параллелизма крайне плохая, по сути, он полностью вырождается в последовательный. Синглтон нужно инициализировать только один раз, но даже после инициализации нельзя избежать синхронизированной блокировки, поэтому getInstance() полностью стала последовательной операцией.Рекомендуется использовать в сценариях, нечувствительных к производительности..
Полный мужчина - Вариант 2
Вариант 2 - "пресловутый"DCL 1.0
.
Ввиду проблемы невозможности избежать блокировки после одноэлементной инициализации в варианте 1, вариант 2 имеет еще один слой проверки во внешнем слое варианта 1, плюс проверка во внутреннем слое синхронизированного, т. е. "Double Check Lock" (Двойная проверка блокировки), именуемая DCL).
// 饱汉
// UnThreadSafe
public class Singleton1_2 {
private static Singleton1_2 singleton = null;
private Singleton1_2() {
}
public static Singleton1_2 getInstance() {
// may get half object
if (singleton == null) {
synchronized (Singleton1_2.class) {
if (singleton == null) {
singleton = new Singleton1_2();
}
}
}
return singleton;
}
}
Ядром варианта 2 является DCL, и кажется, что вариант 2 достиг желаемого эффекта: ленивая загрузка + безопасность потоков. К сожалению, как указано в комментариях, DCL по-прежнему небезопасен для потоков, и вы можете получить «половину объекта» из-за переупорядочения инструкций. После подробного прочтения Варианта 3 вы можете обратиться к предыдущей статье Monkey, которая не будет повторяться здесь.
Ссылаться на:Роль и принцип ключевого слова volatile
Полный мужчина - Вариант 3
Вариант 3 конкретно нацелен на вариант 2, который можно описать какDCL 2.0
.
Чтобы решить проблему "полуобъекта" варианта 3, в варианте 3 к экземпляру добавляется ключевое слово volatile. Принцип см. в приведенной выше ссылке.
// 饱汉
// ThreadSafe
public class Singleton1_3 {
private static volatile Singleton1_3 singleton = null;
private Singleton1_3() {
}
public static Singleton1_3 getInstance() {
if (singleton == null) {
synchronized (Singleton1_3.class) {
// must be a complete instance
if (singleton == null) {
singleton = new Singleton1_3();
}
}
}
return singleton;
}
}
В многопоточной средеВариант 3 больше подходит для сценариев, чувствительных к производительности.. Но мы узнаем позже, даже если это потокобезопасно, и какой-то способ уничтожить одиночный кейс.
режим голодного человека
В отличие от сытого человека, голодный человек очень голоден и просто хочет поесть как можно скорее. Так он инициализирует синглтон при первой же возможности, то есть при загрузке класса, и возвращает напрямую при обращении к нему позже.
// 饿汉
// ThreadSafe
public class Singleton2 {
private static final Singleton2 singleton = new Singleton2();
private Singleton2() {
}
public static Singleton2 getInstance() {
return singleton;
}
}
Преимущество Hungry заключается в том, что он по своей природе потокобезопасен (благодаря механизму загрузки классов), его очень просто писать и при его использовании нет задержки; недостатком является то, что он может привести к пустой трате ресурсов (если singleton никогда не используется после загрузки класса).
Стоит отметить, что в однопоточной среде нет разницы в производительности между голодным человеком и голодным человеком, но в многопоточной среде, поскольку голодный человек должен быть заблокирован, производительность голодного человека это лучше.
Режим держателя
Мы не только надеемся воспользоваться удобством и потокобезопасностью статических переменных в голодном режиме, но также надеемся избежать траты ресурсов за счет отложенной загрузки. Режим Holder удовлетворяет этим двум требованиям: ядро по-прежнему составляют статические переменные, которые достаточно удобны и потокобезопасны; статический класс Holder содержит реальные экземпляры, что косвенно реализует ленивую загрузку.
// Holder模式
// ThreadSafe
public class Singleton3 {
private static class SingletonHolder {
private static final Singleton3 singleton = new Singleton3();
private SingletonHolder() {
}
}
private Singleton3() {
}
/**
* 勘误:多写了个synchronized。。
public synchronized static Singleton3 getInstance() {
return SingletonHolder.singleton;
}
*/
public static Singleton3 getInstance() {
return SingletonHolder.singleton;
}
}
По сравнению с режимом Hungry Man, режим Hold только увеличивает стоимость статического внутреннего класса, что сравнимо с вариантом 3 Satisfied Man (немного лучше) и является более популярной реализацией.Также рекомендуется учитывать.
режим перечисления
Реализация одноэлементного шаблона с перечислениями довольно проста в использовании, но читабельность отсутствует.
базовая нумерация
Сделайте статическую переменную-член перечисления экземпляром синглтона:
// 枚举
// ThreadSafe
public enum Singleton4 {
SINGLETON;
}
Объем кода меньше, чем в режиме Hungry Man. Но пользователь может получить доступ только к экземпляру напрямуюSingleton4.SINGLETON
- На самом деле, этот метод доступа также подходит для использования в качестве синглтона, но за счет преимуществ статических фабричных методов, таких как невозможность добиться отложенной загрузки.
Уродливый, но полезный синтаксический сахар
Перечисления Java — это «уродливый, но полезный синтаксический сахар».
Суть перечисляемого одноэлементного паттерна
Декомпилируя (jad,Исходный код|Оптимизация операции конкатенации строк "+"?Тоже используется) Открываешь синтаксический сахар, там видна суть типа перечисления, которое упрощенно выглядит следующим образом:
// 枚举
// ThreadSafe
public class Singleton4 extends Enum<Singleton4> {
...
public static final Singleton4 SINGLETON = new Singleton4();
...
}
По сути, то же самое, что и голодный китайский режим, единственное отличие состоит в общедоступных статических переменных-членах.
Реализовать некоторые трюки с перечислениями
Эта часть не имеет ничего общего с синглтонами и может быть пропущена. Если вы решите прочитать, обратите внимание на этот факт:Хотя перечисления достаточно гибкие, их правильное использование может быть затруднено.. Типичным достаточно простым примером является класс TimeUnit, и рекомендуется успеть его терпеливо прочитать.
Как вы видели выше, сущность перечисляемого синглтона по-прежнему остается обычным классом. На самом деле, мы можем сделать это на перечислимых синглтонах.УвеличиватьВсе обычный класс может сделать. Дело в том, что инициализация экземпляра перечисления может быть понята как создание анонимного внутреннего класса. Быть более очевидным, мы находимся вSingleton4_1
Определите обычную закрытую переменную-член, обычный общедоступный метод-член и общедоступный абстрактный метод-член следующим образом:
// 枚举
// ThreadSafe
public enum Singleton4_1 {
SINGLETON("enum is the easiest singleton pattern, but not the most readable") {
public void testAbsMethod() {
print();
System.out.println("enum is ugly, but so flexible to make lots of trick");
}
};
private String comment = null;
Singleton4_1(String comment) {
this.comment = comment;
}
public void print() {
System.out.println("comment=" + comment);
}
abstract public void testAbsMethod();
public static Singleton4_1 getInstance() {
return SINGLETON;
}
}
Таким образом, класс перечисленияSingleton4_1
Каждый экземпляр перечисления не только наследуется от родительского классаSingleton4_1
членский методprint()
, также должен реализовать родительский классSingleton4_1
Абстрактный метод членаtestAbsMethod()
.
Суммировать
Приведенный выше анализ игнорирует проблемы отражения и сериализации.Посредством отражения или сериализации мы по-прежнему можем обращаться к приватным конструкторам, создавать новые экземпляры и нарушать одноэлементный шаблон.. В настоящее время только режим перечисления может естественным образом предотвратить эту проблему. Обезьяны отражения и сериализации не очень хорошо изучены, но основные принципы несложны и могут быть реализованы вручную на других шаблонах.
Продолжим игнорировать проблемы отражения и сериализации, и подведем итоги и послевкусие:
Метод реализации | ключевой момент | Пустая трата ресурсов | потокобезопасность | Производительность многопоточной среды достаточно оптимизирована |
---|---|---|---|---|
основная сытость | ленивая загрузка | нет | нет | - |
Очень разнообразие 1 | Ленивая загрузка, синхронизация | нет | да | нет |
полный мужчина вариант 2 | Ленивая загрузка, DCL | нет | нет | - |
полный мужской вариант 3 | Ленивая загрузка, DCL, volatile | нет | да | да |
голодный человек | Инициализация статической переменной | да | да | да |
Holder | Инициализация статической переменной, держатель | нет | да | да |
перечислить | Суть перечисления, инициализация статической переменной | нет | да | да |
Одноэлементный шаблон — это обычная контрольная точка на собеседованиях, и его очень просто написать. С одной стороны, проверьте правильность, см. анализ этой статьи, с другой стороны, проверьте стиль кодирования, см.:Несколько основных правил, которые должны помнить программисты
Ссылка на эту статью:Сколько способов написать одноэлементный шаблон в интервью?
автор:обезьяна 007
Источник:monkeysayhi.github.io
Эта статья основана наCreative Commons Attribution-ShareAlike 4.0Международное лицензионное соглашение выпущено, его можно перепечатать, вывести или использовать в коммерческих целях, но авторство и ссылка на эту статью должны быть сохранены.