предисловие
Одноэлементный шаблон является самым простым из шаблонов проектирования Java. Для реализации одноэлементного шаблона требуется только один класс. Однако нельзя недооценивать одноэлементный шаблон. Несмотря на то, что он относительно прост по своей конструкции, его необходимо реализовать. будет много ям, так что пристегните ремни и садитесь в машину.
Определение одноэлементного шаблона
Одноэлементный режим заключается в создании экземпляра только один раз во время работы программы и создании глобально уникального объекта, который немного похож на статическую переменную в Java, но одноэлементный режим лучше, чем статическая переменная.Статическая переменная будет загружена JVM. когда программа запускается. Если она не используется, это приведет к трате ресурсов. Одноэлементный режим может реализовать ленивую загрузку и может создавать экземпляр только тогда, когда экземпляр используется. Многие классы инструментов в библиотеке классов инструментов разработки применяют одноэлементный шаблон, пропорциональный пул потоков, кеш, объект журнала и т. д. Им нужно создать только один объект.Если создается несколько экземпляров, это может привести к непредсказуемым проблемам, таким как пустая трата ресурсов. , противоречивая обработка результатов и т. д.
Идеи реализации синглтона
- Статический экземпляр объекта
- Приватные конструкторы, запретить создание экземпляров через конструкторы
- Предоставляет общедоступный статический метод, который возвращает уникальный экземпляр
Преимущества синглтонов
-
Только один объект, низкие затраты памяти, хорошая производительность
-
Избегайте многократного занятия ресурсов
-
Установите глобальные точки доступа в системе для оптимизации и совместного доступа к ресурсам.
Реализация одноэлементного шаблона
Существует пять способов написания одноэлементного режима: голодный режим, ленивый режим, режим блокировки с двойной проверкой, одноэлементный режим статического внутреннего класса и класс перечисления для реализации одноэлементного режима Среди них два способа: ленивый режим и режим блокировки с двойной проверкой. Если вы неправильно написали, в случае многопоточности возникнут проблемы, такие как отсутствие синглтона или исключение синглтона Конкретные причины будут объяснены в соответствующем разделе ниже. Мы начинаем наш путь написания синглтона с самого простого голодного режима.
режим голодного человека
Голодный режим принимает простую и грубую форму и непосредственно создает экземпляры объектов при определении статических свойств. код показывает, как показано ниже:
//在类加载时就完成了初始化,所以类加载较慢,但获取对象的速度快
public class SingletonObject1 {
// 利用静态变量来存储唯一实例
private static final SingletonObject1 instance = new SingletonObject1();
// 私有化构造函数
private SingletonObject1(){
// 里面可能有很多操作
}
// 提供公开获取实例接口
public static SingletonObject1 getInstance(){
return instance;
}
}
Преимущества и недостатки режима голодного человека
преимущество
- Благодаря использованию статического ключевого слова гарантируется, что при обращении к переменной все операции записи в эту переменную будут завершены, поэтому гарантируется потокобезопасность на уровне JVM.
недостаток
- Ленивая загрузка не может быть реализована, что приводит к пустой трате места.Если класс относительно большой, мы загружаем класс во время инициализации, но мы не использовали этот класс в течение длительного времени, что приводит к пустой трате памяти.
ленивый режим
Ленивый режим - это ленивый режим. Экземпляр не создается при инициализации программы, а экземпляр создается только тогда, когда экземпляр используется. Таким образом, ленивый режим решает проблему пустой траты места, вызванную голодным режимом, а также представляет другую проблему, давайте посмотрим на следующий ленивый режим
public class SingletonObject2 {
// 定义静态变量时,未初始化实例
private static SingletonObject2 instance;
// 私有化构造函数
private SingletonObject2(){
}
public static SingletonObject2 getInstance(){
// 使用时,先判断实例是否为空,如果实例为空,则实例化对象
if (instance == null)
instance = new SingletonObject2();
return instance;
}
}
Выше приведена реализация ленивого режима, но приведенный выше код небезопасен в случае многопоточности, потому что нельзя гарантировать, что это одноэлементный режим, может быть несколько экземпляров, а случай нескольких экземпляров вызывается при создании экземпляра объекта. Поэтому я выдвинул код создания экземпляра отдельно, чтобы проанализировать, почему существует несколько экземпляров.
1 if (instance == null)
2 instance = new SingletonObject2();
Предположим, что два потока входят в позицию 1, потому что меры защиты ресурсов отсутствуют, поэтому два потока могут судить одновременно.instance
Все пусты, все будут выполнять код создания экземпляра 2, поэтому будет несколько экземпляров.
Благодаря приведенному выше анализу мы уже знаем причины возникновения нескольких экземпляров.Если мы реализуем защиту ресурсов при создании экземпляра, можно ли решить проблему множественных экземпляров? Действительно, мы даемgetInstance()
метод плюсsynchronized
ключевые слова, созданиеgetInstance()
Проблему множественных экземпляров можно решить, сделав метод защищенным ресурсом. плюсsynchronized
Код после ключевого слова выглядит следующим образом:
public class SingletonObject3 {
private static SingletonObject3 instance;
private SingletonObject3(){
}
public synchronized static SingletonObject3 getInstance(){
/**
* 添加class类锁,影响了性能,加锁之后将代码进行了串行化,
* 我们的代码块绝大部分是读操作,在读操作的情况下,代码线程是安全的
*
*/
if (instance == null)
instance = new SingletonObject3();
return instance;
}
}
После модификации мы решили проблему нескольких экземпляров, но из-за добавленияsynchronized
ключевое слово, добавление блокировки в код вводит новую проблему. После блокировки программа станет сериализованной. Только поток, захвативший блокировку, может выполнить этот блок кода, что значительно снизит производительность системы.
Преимущества и недостатки ленивого режима
преимущество
- Реализована ленивая загрузка, экономящая место в памяти
недостаток
- В случае отсутствия блокировки поток небезопасен, и может быть несколько экземпляров.
- В случае блокировки программа будет сериализована, что приведет к серьезным проблемам с производительностью системы.
Режим блокировки с двойной проверкой
Давайте обсудим проблему блокировки в ленивом режиме.getInstance()
С точки зрения метода, большинство операций являются операциями чтения, а операции чтения являются потокобезопасными, поэтому нам не нужно заставлять каждый поток удерживать блокировку для вызова этого метода, нам нужно решить проблему блокировки. Это также привело к новому шаблону реализации:Режим блокировки с двойной проверкой, ниже приведен блок кода одноэлементной реализации для режима блокировки с двойной проверкой:
public class SingletonObject4 {
private static SingletonObject4 instance;
private SingletonObject4(){
}
public static SingletonObject4 getInstance(){
// 第一次判断,如果这里为空,不进入抢锁阶段,直接返回实例
if (instance == null)
synchronized (SingletonObject4.class){
// 抢到锁之后再次判断是否为空
if (instance == null){
instance = new SingletonObject4();
}
}
return instance;
}
}
Режим блокировки с двойной проверкой — это очень хороший режим реализации синглтона, который решает проблемы одноэлементности, производительности и безопасности потоков Вышеупомянутый режим блокировки с двойной проверкой выглядит идеально, но на самом деле есть проблема. резьба, может быть появитсяПроблема с нулевым указателем, причина проблемы в том, что JVM выполняет операции оптимизации и переупорядочения инструкций при создании экземпляров объектов. Что такое изменение порядка инструкций? , посмотрите на следующий пример, краткое понимание заказа из заказа
private SingletonObject4(){
1 int x = 10;
2 int y = 30;
3 Object o = new Object();
}
вышеуказанный конструкторSingletonObject4()
, порядок, который мы записали, — 1, 2, 3, и JVM изменит порядок своих инструкций, поэтому порядок выполнения может быть 3, 1, 2 или 2, 3, 1, независимо от того, какой порядок выполнения, JVM последний Все экземпляры гарантированно будут созданы. Если в конструкторе много операций, для повышения эффективности JVM вернет объект, когда все свойства в конструкторе не будут созданы. Причина проблемы с нулевым указателем в блокировках с двойным обнаружением заключается в следующем. Когда поток получает блокировку для создания экземпляра, другие потоки напрямую получают экземпляр для использования. Из-за переупорядочения инструкций JVM объекты, полученные другими потоками, могут быть неполными. объект, поэтому при использовании экземпляра будет исключение нулевого указателя.
Чтобы решить проблему режима блокировки с двойной проверкой, вызывающего исключение нулевого указателя, просто используйтеvolatile
ключевые слова,volatile
Ключевые слова строго соблюдаютсяhappens-before
Принцип заключается в том, что перед операцией чтения должна быть полностью завершена операция записи. Добавить кvolatile
Код шаблона Singleton после ключевого слова:
// 添加volatile关键字
private static volatile SingletonObject5 instance;
private SingletonObject5(){
}
public static SingletonObject5 getInstance(){
if (instance == null)
synchronized (SingletonObject5.class){
if (instance == null){
instance = new SingletonObject5();
}
}
return instance;
}
}
Добавить кvolatile
Режим блокировки с двойной проверкой после ключевого слова является лучшим режимом одноэлементной реализации, который может обеспечить безопасность потоков без проблем с производительностью в случае многопоточности.
Одноэлементный шаблон статического внутреннего класса
Режим синглтона статического внутреннего класса также называется режимом держателя синглтона. Экземпляр создается внутренним классом. Поскольку JVM не будет загружать статический внутренний класс во время процесса загрузки внешнего класса, только свойства/методы внутреннего будут загружены, а его статические свойства будут инициализированы. статические свойстваstatic
Украшенный, гарантированно созданный только один раз, и порядок создания строго гарантирован. Код одноэлементного шаблона статического внутреннего класса выглядит следующим образом:
public class SingletonObject6 {
private SingletonObject6(){
}
// 单例持有者
private static class InstanceHolder{
private final static SingletonObject6 instance = new SingletonObject6();
}
//
public static SingletonObject6 getInstance(){
// 调用内部类属性
return InstanceHolder.instance;
}
}
Одноэлементный шаблон статического внутреннего класса является превосходным одноэлементным шаблоном и широко используется в проектах с открытым исходным кодом. При отсутствии каких-либо блокировок безопасность при многопоточности гарантирована, нет влияния на производительность и потери места.
Класс перечисления реализует шаблон singleton
Класс перечисления, реализующий шаблон singleton, является шаблоном реализации singleton, настоятельно рекомендуемым эффективными авторами Java, поскольку тип перечисления является потокобезопасным и загружается только один раз, разработчик в полной мере использует эту функцию перечисления для реализации шаблона singleton, перечисление очень прост в написании, а тип перечисления является единственным шаблоном реализации синглтона, который не будет уничтожен в используемой реализации синглтона.
public class SingletonObject7 {
private SingletonObject7(){
}
/**
* 枚举类型是线程安全的,并且只会装载一次
*/
private enum Singleton{
INSTANCE;
private final SingletonObject7 instance;
Singleton(){
instance = new SingletonObject7();
}
private SingletonObject7 getInstance(){
return instance;
}
}
public static SingletonObject7 getInstance(){
return Singleton.INSTANCE.getInstance();
}
}
Методы и решения для уничтожения одноэлементного паттерна
1.Кроме метода перечисления,другие методы уничтожат синглтон через отражение.Отражение генерирует новый объект вызовом конструктора,поэтому если мы хотим предотвратить уничтожение синглтона,можем судить в конструкторе.Если есть экземпляр , это предотвращает создание нового экземпляра.Решение заключается в следующем:
private SingletonObject1(){
if (instance !=null){
throw new RuntimeException("实例已经存在,请通过 getInstance()方法获取");
}
}
2. Если класс синглтона реализует интерфейс сериализации Serializable, синглтон может быть уничтожен десериализацией, поэтому мы не можем реализовать интерфейс сериализации.Если нам нужно реализовать интерфейс сериализации, мы можем переписать метод десериализации readResolve(), связанный объект singleton возвращается непосредственно при десериализации.
public Object readResolve() throws ObjectStreamException {
return instance;
}
Если вы считаете, что статья хорошая, поставьте лайк и перешлите ее
Наконец
Сделайте небольшую рекламу, добро пожаловать, чтобы отсканировать код и подпишитесь на общедоступную учетную запись WeChat: «Технический блог брата Пинтоу», давайте вместе добьемся прогресса.