Все знакомы с моделью Синглтона, и все они знают, почему это ленивое, голодное и т. Д. Но есть ли у вас тщательное понимание шаблона Синглтона? Сегодня я заберу вас, чтобы увидеть синглтонов в моих глазах, что может отличаться от вашего понимания.
Вот простой маленький пример:
// простой ленивый открытый класс Singleton {// inducton ancement varible private static singleton ancement = null; // Частный метод строительства, чтобы убедиться, что внешние классы не могут быть созданы через конструктор private singleton () {} // Получить экземпляр Singleton public static singleton getInstance () {if (exance == null) {exance = new singleton (); } System.out.println («Я простой ленивый синглтон!»); вернуть экземпляр; }}Легко увидеть, что приведенный выше код небезопасен в случае многопоточного. Когда два потока вводят if (exante == null), оба потока судят, что экземпляр пуст, а затем будут получены два экземпляра. Это не тот синглтон, который мы хотим.
Затем мы используем блокировку для достижения взаимного исключения, чтобы обеспечить реализацию синглетонов.
// Синхронный метод Lazy Public Class Singleton {// Синглтон экземпляр переменная частный статический экземпляр Singleton = null; // Частный метод строительства, чтобы убедиться, что внешние классы не могут быть созданы через конструктор private singleton () {} // Получить экземпляр Singleton public Static Synchronized Singleton getInstance () {if (ancess == null) {exants = new Singleton (); } System.out.println ("Я синхронный метод Lazy Singleton!"); вернуть экземпляр; }}Добавление синхронизации обеспечивает безопасность потока, но это лучший способ? Очевидно, что это не так, потому что таким образом, каждый раз, когда мы называем метод getInstance (), мы будем заблокированы, и нам нужно заблокировать его только тогда, когда мы вызовываем getInstance () в первый раз. Это, очевидно, влияет на производительность нашей программы. Мы продолжаем находить лучшие способы.
После анализа было обнаружено, что только путем обеспечения того, чтобы exance = new Singleton () является взаимным исключением потока, можно обеспечить безопасность потока, поэтому доступна следующая версия:
// Double Lock Lazy Public Class Singleton {// Синглтон переменная экземпляра частного статического экземпляра Singleton = null; // Частный метод строительства, чтобы убедиться, что внешние классы не могут быть созданы через конструктор private singleton () {} // получить экземпляр Singleton public static singleton getInstance () {if (exant == null) {synchronized (singleton.class) {if (exant == null) {exants = new singleton (); }}} System.out.println ("Я - двойная блокировка Lazy Singleton!"); вернуть экземпляр; }} На этот раз кажется, что не только решает проблему безопасности потока, но и не приводит к добавлению блокировки каждый раз, когда вы называете getInstance (), что приводит к снижению производительности. Это похоже на идеальное решение, на самом деле это так?
К сожалению, факт не так совершенен, как мы думали. В модели памяти на платформе Java есть механизм, называемый «вне порядок». Именно этот механизм вызывает сбой метода блокировки двойной проверки. Ключ к этой проблеме заключается в строке 5 в приведенном выше коде: экземпляры = new Singleton (); Эта строка на самом деле делает две вещи: 1. Вызовите конструктор и создайте экземпляр. 2. Назначьте этот экземпляр экземпляру переменной экземпляра. Но проблема в том, что эти два шага JVM не гарантируют порядок. То есть. Может случиться так, что экземпляр был установлен на непусты, прежде чем вызовать конструктор. Давайте проанализируем это вместе:
Предположим, есть две нити A и B
1. ТИНГ А Входит в метод getInstance ().
2. Поскольку экземпляр пуст в это время, потоки A входит в синхронизированный блок.
3. потока a exception exance = new singleton (); Устанавливает экземпляр переменной переменной экземпляра на непусты. (Обратите внимание, что это будет перед вызовом конструктора.)
4. Торд выходов и вход в резьбу B.
5. Поток B проверяет, является ли экземпляр пусты, и в настоящее время он не пуст (на третьем этапе он устанавливается на непусты по потоку A). Поток B возвращает ссылку на экземпляр. (Проблема возникает. В настоящее время ссылка на экземпляр не является экземпляром Синглтона, потому что конструктор не называется.)
6. Поток B выходит и входит в систему.
7. Тонка А продолжает вызовать метод конструктора, завершает инициализацию экземпляра и возвращает.
Нет хорошего способа? Должен быть хороший способ, давайте продолжим исследовать!
// Решить проблему неупорядоченного написания ленивого открытого класса Singleton {// inducton ancement переменная частный статический экземпляр Singleton = null; // Частный метод строительства, чтобы убедиться, что внешние классы не могут быть созданы через конструктор private singleton () {} // получить экземпляр Singleton public static singleton getInstance () {if (exant == null) {synchronized (singleton.class) {// 1 inemplon temp = exance; // 2 if (temp == null) {synchronized (singleton.class) {// 3 temp = new singleton (); // 4} encement = temp; // 5}}} System.out.println ("Я решаю неупорядоченное написание ленивых синглтонов!"); вернуть экземпляр; }} 1. ТИНГ А Входит в метод getInstance ().
2. Поскольку экземпляр пуст, резьба А входит в первый синхронизированный блок в положении // 1.
3. потока A выполняет код в положении // 2 и назначает экземпляр локальной переменной температуре. экземпляр пуст, поэтому темп также пуста.
4. Поскольку температура пуста, резьба А входит во второй синхронизированный блок в положении // 3. (Я думал, что этот замок был немного избыточным)
5. потока A выполняет код в положении // 4, устанавливая температуру для непусты, но конструктор еще не был вызван! («Неупорядочная проблема написания»)
6. Если резьба A блоки, поток B входит в метод GetInstance ().
7. Поскольку экземпляр пуст, нить B пытается ввести первый синхронизированный блок. Но потому что нить А уже внутри. Так что в это невозможно войти. Поток B -блоки.
8. Поток A активируется и продолжает выполнять код в положении // 4. Вызовите конструктор. Генерировать экземпляр.
9. Назначьте экземпляр ссылки на экземпляр Temp. Выйдите из двух синхронизированных блоков. Возвращает экземпляр.
10. Поток B активируется и входит в первый синхронизированный блок.
11. Поток B выполняет код в положении // 2 и назначает экземпляр экземпляра временной локальной переменной.
12. Поток B определяет, что локальная переменная температура не является пустой, поэтому пропускает блок. Возвращает экземпляр экземпляра.
До сих пор мы решили вышеуказанную проблему, но мы вдруг обнаружили, что для решения проблемы безопасности нити кажется, что на теле есть много пряжи ... это грязно, поэтому нам нужно упростить ее:
// Голодный открытый класс Singleton {// singleton переменная, статическая, инициализируется один раз, когда класс загружен, чтобы обеспечить безопасность потока частного статического экземпляра Singleton = new Singleton (); // Частный метод строительства, чтобы гарантировать, что внешние классы не могут быть созданы через конструктор. private singleton () {} // Получить экземпляр объекта Singleton public static singleton getInstance () {System.out.println («Я голодный Синглтон!»); вернуть экземпляр; }}Когда я увидел код выше, я сразу почувствовал, что мир тихо. Тем не менее, этот метод принимает голодный метод в стиле человека, который предназначен для предварительного декларского синглтона. Одним из недостатков этого является: если синглтон строительства большой и не используется после завершения строительства, это приведет к отходу ресурсов.
Есть идеальный способ? Продолжайте смотреть:
// Внутренний класс реализует ленивый открытый класс Singleton {Private Static Class Singletonholder {// singleton переменная частный статический extment = new Singleton (); } // Частный метод строительства, чтобы гарантировать, что внешние классы не могут быть созданы через конструктор. private singleton () {} // Получить экземпляр объекта Singleton public static singleton getInstance () {System.out.println («Я внутренний класс Singleton!»); return singletonholder.instance; }}Lazy (избегайте отходов ресурсов выше), безопасного потока и простого кода. Поскольку механизм Java предусматривает, что внутренний класс Singletonholder будет загружен только тогда, когда метод getInstance () вызывается в первый раз (реализация ленивого), а его процесс загрузки безопасен для потока (реализация защиты потоков). Экземпляр создается при загрузке внутреннего класса.
Давайте кратко поговорим о неупорядоченном письме, упомянутом выше. Это характеристика JVM. Например, объявление двух переменных, строка a; Строка B; JVM может загрузить первое или б. Аналогично, exante = new Singleton (); может установить экземпляр непусты, прежде чем вызовать конструктор Синглтона. Это вопрос для многих людей, говоря, что объект Синглтона не был создан, так как же экземпляр стал непустым? Какова его ценность сейчас? Если вы хотите понять эту проблему, вы должны понимать, как exance Predence = new Singleton (); выполнено. Вот псевдокод, чтобы объяснить вам:
mem = allocate (); // выделять память для объектов Singleton. экземпляр = mem; // Обратите внимание, что экземпляр сейчас непусты, но еще не инициализирован. Ctorsingleton (экземпляр); // Вызовите конструктор экземпляра Singleton и Pass.
Можно видеть, что когда поток выполняет exance = mem;, экземпляр не является пустым. Если другой поток вступает в экземпляр программы и судьи как непусты, он прыгнет, чтобы вернуть экземпляр; И в настоящее время конструктор Синглтона не называется экземпляром, а текущее значение - это объект памяти, возвращаемый Allocate ();. Таким образом, второй поток получает не одноялтонский объект, а объект памяти.
Вышесказанное - мои маленькие мысли и понимание модели Синглтона. Я тепло приветствую всех великих богов, которые придут и направляют и критикуем.