----- На следующий день -----
Первая версия одноэлементного паттерна:
public class Singleton {
private Singleton() {} //私有构造函数
private static Singleton instance = null; //单例对象
//静态工厂方法
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
Почему ты это пишешь? Давайте объясним несколько ключевых моментов:
1. Если класс может построить только один объект, естественно, он не может случайно выполнить новую операцию, поэтому конструктор Signleton является закрытым.
2.instance — это статический член класса Singleton, который также является нашим объектом singleton. Его начальное значение может быть записано как Null или как new Singleton(). Разница будет объяснена позже.
3.getInstance — это метод получения одноэлементного объекта.
Если начальное значение singleton равно null и еще не создано, создайте объект singleton и верните его. Это письмо относится к шаблону singleton.Ленивый режим.
Если одноэлементный объект активно создается с помощью new Singleton() в начале, нулевая операция больше не требуется.Этот метод записи принадлежитрежим голодного человека.
Эти два имени очень яркие: голодный человек проявляет инициативу, чтобы найти еду, а ленивый лежит на земле, ожидая, пока кто-нибудь поест.
Почему только что код не является потокобезопасным?
Предположим, что класс Singleton только что инициализировался, а объект-экземпляр еще пуст, и в это время два потока одновременно обращаются к методу getInstance:
Поскольку экземпляр пуст, два потока одновременно принимают условное решение и начинают выполнять новую операцию:
В результате экземпляр явно создается дважды. Вносим изменения в код:
Синглтон Узор Второе издание:
public class Singleton {
private Singleton() {} //私有构造函数
private static Singleton instance = null; //单例对象
//静态工厂方法
public static Singleton getInstance() {
if (instance == null) { //双重检测机制
synchronized (Singleton.class){ //同步锁
if (instance == null) { //双重检测机制
instance = new Singleton();
}
}
}
return instance;
}
}
Почему так написано? Поясним несколько ключевых моментов:
1. Чтобы предотвратить многократное выполнение нового синглтона, перед новой операцией добавляется блокировка синхронизации Synchronized для блокировки всего класса (обратите внимание, что здесь нельзя использовать блокировки объектов).
2. После входа в Синхронизированную критическую секцию необходимо сделать еще одно пустое суждение. Потому что, когда два потока обращаются к объекту одновременно, поток A завершил построение объекта, а поток B также прошел первоначальную нулевую оценку.
Механизм двойного суждения, подобный этому, называетсямеханизм двойного обнаружения.
————————————
Предположим такой сценарий, когда два потока один за другим обращаются к методу getInstance, когда поток A строит объект, поток B только что вошел в метод:
Кажется, в этой ситуации нет проблем: либо Экземпляр не был создан потоком A, и поток B получает значение true при выполнении if (instance == null); либо Экземпляр был создан потоком A, а поток B выполняется if ( instance == null), чтобы получить false.
Это действительно так? ответ отрицательный. Это включает в себя компилятор JVMперестановка инструкций.
Что означает изменение порядка инструкций? Например, простое предложение instance = new Singleton в java будет скомпилировано компилятором в следующие инструкции JVM:
Memory = allocate(); // 1: Место в памяти для размещения объектов
ctorInstance(memory); //2: Инициализировать объект
instance =memory; //3: Установите instance так, чтобы он указывал на только что выделенный адрес памяти
Однако порядок этих инструкций не является статическим и может быть оптимизирован JVM и ЦП, а инструкции переупорядочены в следующем порядке:
memory =allocate(); //1: выделить место в памяти объекта
instance =memory; //3: Установите instance так, чтобы он указывал на только что выделенный адрес памяти
ctorInstance(memory); //2: Инициализировать объект
Когда поток A завершает выполнение 1, 3, объект экземпляра не был инициализирован, но больше не указывает на null. В это время, если поток B вытесняет ресурсы ЦП, результат выполнения if (instance == null) будет ложным, таким образом возвращаяОбъект INSTANCE без инициализации. Как показано ниже:
Как избежать этой ситуации? Нам нужно добавить модификатор перед объектом экземпляраизменчивый.
Третье издание шаблона Singleton:
public class Singleton {
private Singleton() {} //私有构造函数
private volatile static Singleton instance = null; //单例对象
//静态工厂方法
public static Singleton getInstance() {
if (instance == null) { //双重检测机制
synchronized (Singleton.class){ //同步锁
if (instance == null) { //双重检测机制
instance = new Singleton();
}
}
}
return instance;
}
}
The volatile keyword indicates that a value may change between different accesses, it prevents an optimizing compiler from optimizing away subsequent reads or writes and thus incorrectly reusing a stale value or omitting writes.
После изменения volatile, когда поток A выполняет instance = new Singleton, каков порядок выполнения JVM? Всегда гарантируется следующий порядок:
memory =allocate(); //1: выделить место в памяти объекта
ctorInstance(memory); //2: Инициализировать объект
instance =memory; //3: Установите instance так, чтобы он указывал на только что выделенный адрес памяти
Таким образом, в представлении потока B ссылка на объект экземпляра указывает либо на null, либо на инициализированный экземпляр, и промежуточного состояния не будет, что обеспечивает безопасность.
Реализуйте шаблон singleton со статическим внутренним классом:
public class Singleton {
private static class LazyHolder {
private static final Singleton INSTANCE = new Singleton();
}
private Singleton (){}
public static Singleton getInstance() {
return LazyHolder.INSTANCE;
}
}
Вот несколько замечаний:
1. К статическому внутреннему классу LazyHolder нельзя получить доступ извне, а объект-синглтон INSTANCE можно получить только при вызове метода Singleton.getInstance.
2. Момент инициализации объекта INSTANCE определяется не моментом загрузки одноэлементного класса Singleton, а моментом вызова метода getInstance для загрузки статического внутреннего класса LazyHolder. Итак, эта реализация используетМеханизм загрузки classloaderЧтобы реализовать ленивую нагрузку и обеспечить безопасность потоков здания синглтонов.
Как использовать отражение, чтобы сломать ограничения одноэлементного шаблона? На самом деле это очень просто, давайте посмотрим на код.
Используйте отражение, чтобы сломать синглтон:
//获得构造器
Constructor con = Singleton.class.getDeclaredConstructor();
//设置为可访问
con.setAccessible(true);
//构造两个不同的对象
Singleton singleton1 = (Singleton)con.newInstance();
Singleton singleton2 = (Singleton)con.newInstance();
//验证是否是不同对象
System.out.println(singleton1.equals(singleton2));
Код можно упростить до трех шагов:
Первый шаг — получить конструктор одноэлементного класса.
Второй шаг — сделать конструктор доступным.
Третий шаг — создать объект с помощью метода newInstance.
Наконец, для того, чтобы подтвердить, действительно ли два объекта действительно разные объекты, мы используем метод равенства для сравнения. Несомненно, результат сравнения является ложным.
Реализуйте одноэлементный шаблон с помощью перечисления:
public enum SingletonEnum {
INSTANCE;
}
Давайте проведем эксперимент, все еще выполняя код отражения прямо сейчас:
//获得构造器
Constructor con = SingletonEnum.class.getDeclaredConstructor();
//设置为可访问
con.setAccessible(true);
//构造两个不同的对象
SingletonEnum singleton1 = (SingletonEnum)con.newInstance();
SingletonEnum singleton2 = (SingletonEnum)con.newInstance();
//验证是否是不同对象
System.out.println(singleton1.equals(singleton2));
При выполнении шага получения конструктора выбрасывается следующее исключение:
Exception in thread "main" java.lang.NoSuchMethodException: com.xiaohui.singleton.test.SingletonEnum.<init>()
at java.lang.Class.getConstructor0(Class.java:2892)
at java.lang.Class.getDeclaredConstructor(Class.java:2058)
at com.xiaohui.singleton.test.SingletonTest.main(SingletonTest.java:22)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:606)
at com.intellij.rt.execution.application.AppMain.main(AppMain.java:134)
Несколько дополнений:
1. Ключевое слово volatile может не только предотвратить перестановку команд, но и гарантировать, что значение переменной, к которому обращается поток,Последнее значение основной памяти. Что касается подробного принципа volatile, я объясню его специально в будущих комиксах.
2. Одноэлементный шаблон, реализованный перечислением, может не только предотвратить использование отражения для принудительного построения одноэлементного объекта, но также может использоваться в объектах класса перечисления.десериализоватьКогда возвращаемый результат десериализации гарантированно будет одним и тем же объектом.
Для одноэлементного шаблона, реализованного другими способами, если вы хотите быть как сериализуемым, так и десериализуемым для одного и того же объекта, вы должны реализоватьreadResolveметод.
3.Этот комикс предназначен исключительно для развлечения, пожалуйста, цените свою текущую работу как можно больше и не подражайте поведению Сяохуэй.
----КОНЕЦ----
Друзья, которым понравилась эта статья, нажмите и удерживайте изображение, чтобы следить за номером подписки.программист маленький серый, смотрите больше захватывающего контента