单例模式(设计模式)

文章目录

    • 概述
    • 懒汉式
    • 饿汉式
    • 双重加锁机制
    • 类级内部类方式
    • 单例模式适用场景
    • Spring 的单例实现原理
    • 单例被破坏的五个场景
    • 单例的实现方式
    • 实现线程安全的单例模式

概述

单例模式:单例对象能保证在一个JVM中,该对象只有一个实例存在。保证被创建一次,节省系统开销
解决的问题:保证一个类在内存中的对象唯一性
  所谓单例,指的就是单实例,有且仅有一个类实例,这个单例不应该由人来控制,而应该由代码来限制,强制单例。
  单例有其独有的使用场景,一般是对于那些业务逻辑上限定不能多例只能单例的情况,例如:类似于计数器之类的存在,
一般都需要使用一个实例来进行记录,若多例计数则会不准确。
  其实单例就是那些很明显的使用场合,没有之前学习的那些模式所使用的复杂场景,只要你需要使用单例,那你就使用单例,简单易理解。
  所以我认为有关单例模式的重点不在于场景,而在于如何使用。

懒汉式

何为懒?顾名思义,就是不做事,这里也是同义,懒汉式就是不在系统加载时就创建类的单例,而是在第一次使用实例的时候再创建。
详见下方代码示例:

public class LHanDanli {//定义一个私有类变量来存放单例,私有的目的是指外部无法直接获取这个变量,而要使用提供的公共方法来获取private static LHanDanli dl = null;//定义私有构造器,表示只在类内部使用,亦指单例的实例只能在单例类内部创建private LHanDanli(){}//定义一个公共的公开的方法来返回该类的实例,由于是懒汉式,需要在第一次使用时生成实例,所以为了线程安全,使用synchronized关键字来确保只会生成单例public static synchronized LHanDanli getInstance(){if(dl == null){dl = new LHanDanli();}return dl;}
}

饿汉式

又何为饿?饿者,饥不择食;但凡有食,必急食之。此处同义:在加载类的时候就会创建类的单例,并保存在类中。
详见下方代码示例:

public class EHanDanli {//此处定义类变量实例并直接实例化,在类加载的时候就完成了实例化并保存在类中private static EHanDanli dl = new EHanDanli();//定义无参构造器,用于单例实例private EHanDanli(){}//定义公开方法,返回已创建的单例public static EHanDanli getInstance(){return dl;}}

双重加锁机制

在懒汉式实现单例模式的代码中,有使用synchronized关键字来同步获取实例,保证单例的唯一性,但是上面的代码在每一次执行时都要进行同步和判断,
DCL是一种单例模式写法的简称,全称是Double Check Lock,翻译过来叫双重检查锁。从命名上来理解,
就是两次检查加一把锁。那么,两次检查又是检查什么,锁又是锁的什么?
从代码中,我们发现两次检查的判断条件都是 null == instance,而且两个检查条件是嵌套的。在第1次检查条
件的代码块中,加了一段synchronized代码块,synchronized就是锁。
相当于,不管单例对象是否已经创建,每次调用都可能阻塞,会影响程序的执行效率。所以,加上第1次检查的
目的是,保证只有第一次出现并发的情况会阻塞,提高性能。
因此,第2次检查的目的是,保证单例,避免重复创建单例对象。
第1次检查是为了保证只有首次并发的情况下才阻塞,提高性能,
第2次检查是为了保证,避免重复创建对象。加锁,当然就是为了保证线程安全。
在今天的分享,我还有一个细节没有讲到,就是在并发情况下,new一个对象可能会出现指令重排的现象。这时
候,我们需要给声明的单例对象加上volatile关键字,保证可见性。
无疑会拖慢速度,使用双重加锁机制正好可以解决这个问题:

public class SLHanDanli {private static volatile SLHanDanli dl = null;private SLHanDanli(){}public static SLHanDanli getInstance(){if(dl == null){synchronized (SLHanDanli.class) {if(dl == null){dl = new SLHanDanli();}}}return dl;}}

看了上面的代码,有没有感觉很无语,双重加锁难道不是需要两个synchronized进行加锁的吗?
  …

其实不然,这里的双重指的的双重判断,而加锁单指那个synchronized,为什么要进行双重判断,其实很简单,第一重判断,如果单例已经存在,
那么就不再需要进行同步操作,而是直接返回这个实例,如果没有创建,才会进入同步块,同步块的目的与之前相同,目的是为了防止有两个调用同时进行时,
导致生成多个实例,有了同步块,每次只能有一个线程调用能访问同步块内容,当第一个抢到锁的调用获取了实例之后,这个实例就会被创建,之后的所有调用
都不会进入同步块,直接在第一重判断就返回了单例。至于第二个判断,个人感觉有点查遗补漏的意味在内(期待高人高见)。
  补充:关于锁内部的第二重空判断的作用,当多个线程一起到达锁位置时,进行锁竞争,其中一个线程获取锁,如果是第一次进入则dl为null,会进行单例对象的创建,完成后释放锁,其他线程获取锁后就会被空判断拦截,直接返回已创建的单例对象。
  不论如何,使用了双重加锁机制后,程序的执行速度有了显著提升,不必每次都同步加锁。
  其实我最在意的是volatile的使用,volatile关键字的含义是:被其所修饰的变量的值不会被本地线程缓存,所有对该变量的读写都是直接操作共享内存来实现,
从而确保多个线程能正确的处理该变量。该关键字可能会屏蔽掉虚拟机中的一些代码优化,所以其运行效率可能不是很高,所以,一般情况下,并不建议使用双重
加锁机制,酌情使用才是正理!
  更进一步说,其实使用volatile的目的是为了防止暴露一个未初始化的不完整单例实例,导致系统崩溃。因为创建单例实例其实需要经过以下几步:
首先分配内存空间、然后将内存空间的首地址指向引用(指针),最后调用构造器创建实例,由于在第二步的时候这个引用(指针)就会变的非null,
那么在第三步未执行,真正的单例实例还未创建完成的时候,一个线程过来在第一个校验中为false,将会直接将不完整的实例返回,从而造成系统崩溃。

类级内部类方式

饿汉式会占用较多的空间,因为其在类加载时就会完成实例化,而懒汉式又存在执行速率慢的情况,双重加锁机制呢?又有执行效率差的毛病,
有没有一种完美的方式可以规避这些毛病呢?
  貌似有的,就是使用类级内部类结合多线程默认同步锁,同时实现延迟加载和线程安全。

public class ClassInnerClassDanli {public static class DanliHolder{private static ClassInnerClassDanli dl = new ClassInnerClassDanli();}private ClassInnerClassDanli(){}public static ClassInnerClassDanli getInstance(){return DanliHolder.dl;}}

如上代码,所谓类级内部类,就是静态内部类,这种内部类与其外部类之间并没有从属关系,加载外部类的时候,并不会同时加载其静态内部类,
只有在发生调用的时候才会进行加载,加载的时候就会创建单例实例并返回,有效实现了懒加载(延迟加载),至于同步问题,我们采用和饿汉式
同样的静态初始化器的方式,借助JVM来实现线程安全。
  其实使用静态初始化器的方式会在类加载时创建类的实例,但是我们将实例的创建显式放置在静态内部类中,它会导致在外部类加载时不进行实例创建,
这样就能实现我们的双重目的:延迟加载和线程安全。

单例模式适用场景

好多没怎么使用过的人可能会想,单例模式感觉不怎么用到,实际的应用场景有哪些呢?以下,我将列出一些就在咱们周边和很有意义的单例应用场景。

  1. Windows的Task Manager(任务管理器)就是很典型的单例模式(这个很熟悉吧),想想看,是不是呢,你能打开两个windows task manager吗? 不信你自己试试看哦~

  2. windows的Recycle Bin(回收站)也是典型的单例应用。在整个系统运行过程中,回收站一直维护着仅有的一个实例。

  3. 网站的计数器,一般也是采用单例模式实现,否则难以同步。

  4. 应用程序的日志应用,一般都何用单例模式实现,这一般是由于共享的日志文件一直处于打开状态,因为只能有一个实例去操作,否则内容不好追加。

  5. Web应用的配置对象的读取,一般也应用单例模式,这个是由于配置文件是共享的资源。

  6. 数据库连接池的设计一般也是采用单例模式,因为数据库连接是一种数据库资源。数据库软件系统中使用数据库连接池,主要是节省打开或者关闭数据库
    连接所引起的效率损耗,这种效率上的损耗还是非常昂贵的,因为何用单例模式来维护,就可以大大降低这种损耗。

  7. 多线程的线程池的设计一般也是采用单例模式,这是由于线程池要方便对池中的线程进行控制。

  8. 操作系统的文件系统,也是大的单例模式实现的具体例子,一个操作系统只能有一个文件系统。

  9. HttpApplication 也是单位例的典型应用。熟悉ASP.Net(IIS)的整个请求生命周期的人应该知道HttpApplication也是单例模式,所有的HttpModule都共享
    一个HttpApplication实例.

    1.需要生成唯一序列的环境
    2.需要频繁实例化然后销毁的对象。
    3.创建对象时耗时过多或者耗资源过多,但又经常用到的对象。
    4.方便资源相互通信的环境
    优点:1.实现了对唯一实例访问的可控
    2.对于一些需要频繁创建和销毁的对象来说可以提高系统的性能。
    缺点:1. 不适用于变化频繁的对象
    2.滥用单例将带来一些负面问题,如为了节省资源将数据库连接池对象设计为的单例类,可能会导致共享连接池对象的程序过多而出现连接池溢出。
    3.如果实例化的对象长时间不被利用,系统会认为该对象是垃圾而被回收,这可能会导致对象状态的丢失。

Spring 的单例实现原理

在Spring中,Bean默认是单例的,这是因为Spring容器在初始化时会将Bean对象创建并缓存在容器中,默认情况下,Spring容器使用单例模式来管理Bean实例。
以下是 Spring 实现单例的主要步骤和原理:

  1. Bean 定义:在 Spring 的配置文件中(如 XML 文件)或通过注解方式,我们定义 Bean 及其相关属性。其中,scope 属性用于指定 Bean 的作用域,默认值是 “singleton”,表示该 Bean 是一个单例。
  2. 容器初始化:当 Spring 容器启动时,它会读取配置文件或注解信息,并解析 Bean 的定义。对于每个单例 Bean,Spring 容器会创建一个实例,并将其存储在容器的内部缓存中。
  3. Bean 获取:当应用中的代码通过 ApplicationContext 或 BeanFactory 调用 getBean() 方法来获取某个单例 Bean 时,Spring 容器会首先检查其内部缓存。如果该 Bean 已经存在(即已经被创建过),则直接返回该实例;否则,根据 Bean 的定义创建一个新的实例,并存储在缓存中,然后返回该实例。
  4. 单例保证:由于 Spring 容器在内部缓存了单例 Bean 的实例,并且在每次获取 Bean 时都优先从缓存中查找,因此确保了在整个应用上下文中,对于同一个单例 Bean 的定义,始终返回同一个实例。
  5. 容器销毁:当 Spring 容器被销毁时,它会负责销毁所有它管理的 Bean,包括单例 Bean。这确保了资源的正确释放和避免内存泄漏。
    需要注意的是,虽然 Spring 保证了单例 Bean 在容器范围内的唯一性,但并不意味着它在整个 JVM 中都是唯一的。如果有多个 Spring 容器(例如,每个 Web 应用都有一个自己的 Spring 容器),那么每个容器都会创建自己的单例 Bean 实例。此外,对于原型(prototype)作用域的 Bean,Spring 每次都会创建一个新的实例,而不是共享同一个实例。
    Spring的单例实现原理主要基于两个方面:
  6. 默认作用域: 在Spring中,默认情况下,Bean的作用域(Scope)是单例(Singleton)。这意味着Spring容器中的每个Bean定义都只会创建一个实例,并在需要时重复使用这个实例。
  7. 容器管理: Spring容器是一个大型的对象管理容器,它负责创建、装配和管理Bean对象。当配置文件或注解启动Spring容器时,容器会按照配置创建Bean的实例并管理它们的生命周期。容器会在启动时实例化所有的单例Bean,并在需要时返回它们的引用,以确保单例的唯一性。
    需要注意的是,虽然Spring默认将Bean配置为单例模式,但也可以通过在Bean的定义中显式地指定其他作用域(如原型、请求、会话等)来改变Bean的作用域。例如,在XML配置文件中可以使用元素的scope属性,或者在使用注解配置时可以使用@Scope注解来指定Bean的作用域。
    总的来说,Spring的单例实现原理是基于容器管理和作用域定义的,它确保在Spring容器中每个Bean的实例是唯一的,并且可以在需要时被共享和重用。
    Spring 框架中的单例实现原理主要依赖于其 IoC(控制反转)容器。在 Spring 中,当我们定义一个 Bean 时,Spring 容器会负责创建和管理这个 Bean 的生命周期。对于单例模式的 Bean,Spring 容器会确保只创建一个实例,并在整个应用上下文中共享这个实例。

单例被破坏的五个场景

分别为多线程破坏单例、指令重排破坏单例、克隆破坏单例、反序列化破坏单例、反射破坏单例。
1.多线程破坏单例
在多线程环境下,线程的时间片是由CPU自由分配的,具有随机性,而单例对象作为共享资源可能会
同时被多个线程同时操作,从而导致同时创建多个对象。当然,这种情况只出现在懒汉式单例中。如果是
饿汉式单例,在线程启动前就被初始化了,不存在线程再创建对象的情况。
如果懒汉式单例出现多线程破坏的情况,我给出以下两种解决方案:
1、改为DCL双重检查锁的写法。
2、使用静态内部类的写法,性能更高。

2.指令重排
指令重排也可能导致懒汉式单例被破坏。来看这样一句代码:
instance = new Singleton();
看似简单的一段赋值语句:instance = new Singleton();
其实JVM内部已经被转换为多条执行指令:
memory = allocate(); 分配对象的内存空间指令
ctorInstance(memory); 初始化对象
instance = memory; 将已分配存地址赋值给对象引用
1、分配对象的内存空间指令,调用allocate()方法分配内存。
2、调用ctorInstance()方法初始化对象
3、将已分配存地址赋值给对象引用
但是经过重排序后,执行顺序可能是这样的:
memory = allocate(); 分配对象的内存空间指令
instance = memory; 将已分配存地址赋值给对象引用
ctorInstance(memory); 初始化对象
1、分配对象的内存空间指令
2、设置instance指向刚分配的内存地址
3、初始化对象
我们可以看到指令重排之后,instance指向分配好的内存放在了前面,而这段内存的初始化的指令被
排在了后面,在线程 T1 初始化完成这段内存之前,线程T2 虽然进不去同步代码块,但是在同步代码块之
前的判断就会发现 instance 不为空,此时线程T2 获得 instance 对象,如果直接使用就可能发生错误。
如果出现这种情况,我该如何解决呢?只需要在成员变量前加volatile,保证所有线程的可见性就可
以了。private static volatile Singleton instance = null;

3.克隆破坏单例
在Java中,所有的类就继承自Object,也就是说所有的类都实现了clone()方法。如果是深clone(),
每次都会重新创建新的实例。那如果我们定义的是单例对象,岂不是也可调用clone()方法来反复创建新的
实例呢?确实,这种情况是有可能发生的。为了避免发生这样结果,我们可以在单例对象中重写clone()
方法,将单例自身的引用作为返回值。这样,就能避免这种情况发生。

4.反序列化破坏单例
我们将Java对象序列化以后,对象通常会被持久化到磁盘或者数据库。如果我们要再次加载到内存,
就需要将持久化的内容反序列化成Java对象。反序列化是基于字节码来操作的,我们要序列化以前的内容
进行反序列化到内存,就需要重新分配内存,也就是说,要重新创建对象。那如果要反序列化的对象恰恰
是单例对象,我们该怎么办呢?
我告诉大家一种解决方案,在反序列的过程中,Java API会调用readResolve()方法,可以通过获取
readResolve()方法的返回值覆盖反序列化创建的对象。
因此,只需要重写readResolve()方法,将返回值设置为已经存在的单例对象,就可以保证反序列化
以后的对象是同一个了。之后再将反序列化后的对象中的值,克隆到单例对象中。

5.反射破坏单例
以上讲的所有单例情况都有可能被反射破坏。因为Java中的反射机制是可以拿到对象的私有的构造方
法,也就是说,反射可以任意调用私有构造方法创建单例对象。当然,没有人会故意这样做,但是如果出
现意外的情况,该如何处理呢?我推荐大家两种解决方案,
第一种方案是在所有的构造方法中第一行代码进行判断,检查单例对象是否已经被创建,如果已经被
创建,则抛出异常。这样,构造方法将会被终止调用,也就无法创建新的实例。
第二种方案,将单例的实现方式改为枚举式单例,因为在JDK源码层面规定了,不允许反射访问枚举。

单例的实现方式

饿汉式单例
懒汉式-延迟加载方式
懒汉式双重加锁机制
静态内部类
容器式单例
注册式-枚举单例
ThreadLocal 线程内部
饿汉式单例
优点:执行效率高,性能高,线程安全
缺点:类加载时用不用都会初始化,资源浪费

package com.lc.singleton;/*** @Author lc* @description:* 1.  * 饿汉式单例* 2.  * 优点:执行效率高,性能高,线程安全* 3.  * 缺点:类加载时用不用都会初始化,资源浪费* @Date 2023/4/1 18:25*/
public class HungrySingleton {private HungrySingleton() {};private static HungrySingleton hungrySingleton = new HungrySingleton();public static HungrySingleton getInstance() {return hungrySingleton;}public static void main(String[] args) {for (int i = 0; i <20 ; i++) {new Thread(()->{System.out.println(HungrySingleton.getInstance().hashCode());}).start();}}
}

懒汉式-延迟加载方式

/**** @Author lc* @description:懒汉式-延迟加载方式* @Date 2023/4/1 16:26*/
public class SingletonLazy {private SingletonLazy(){};private static SingletonLazy singletonLazy ;public static SingletonLazy getInstance(){if(singletonLazy==null){singletonLazy=new SingletonLazy();}return singletonLazy;}}

懒汉式双重加锁机制

package com.lc.singleton.lazy;/*** @Author lc* @description: 懒汉式双重加锁机制* 有使用synchronized关键字来同步获取实例,保证单例的唯一性,使用双重加锁机制正** 懒汉式-双重检查锁* 优点:被外部调用的时候创建对象,节省资源,性能高,线程安全* 缺点:可读性难度加大,代码不够优雅* @Date 2023/4/1 16:26*/
public class SingletonLazyDoubleCheck {private SingletonLazyDoubleCheck() {};//volatile关键字的含义是:被其所修饰的变量的值不会被本地线程缓存,所有对该变量的读写都是直接操作共享内存来实现,// 从而确保多个线程能正确的处理该变量,volatile的目的是为了防止暴露一个未初始化的不完整单例实例,导致系统崩溃private volatile static SingletonLazyDoubleCheck singletonLazy;//volatile来禁止指令重排public static SingletonLazyDoubleCheck getInstance() {//检查是否阻塞,如果已经创建过,就不需要再进入加锁代码块拉//低性能,大大的提升性能,如果没有该检查,每次都会去竞争锁if (singletonLazy == null) {synchronized (SingletonLazyDoubleCheck.class) {//检查是否重新创建实例if (singletonLazy == null) {singletonLazy = new SingletonLazyDoubleCheck();}}}return singletonLazy;}
}

静态内部类

package com.lc.singleton.lazy;/*** @Author lc* @description: 静态内部类* 懒汉式-静态内部类*  * 优点:性能高,节省资源,利用了java本身的语法特点,不能够被反射破坏*  * 缺点:代码不优雅* @Date 2023/4/1 20:39*/
public class SingletonLazyStaticInnerClass {private  SingletonLazyStaticInnerClass(){};private static SingletonLazyStaticInnerClass getInstance(){return lazyHolder.STATIC_INNER_CLASS;}private  static  class lazyHolder{private  static  final  SingletonLazyStaticInnerClass STATIC_INNER_CLASS=new SingletonLazyStaticInnerClass();}
}

容器式单例

package com.lc.singleton.register;import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;/*** @Author lc* @description: 容器式单例  目前线程不安全 ,解决方案 加锁* 可参考spring--AbstractFactoryBean  getBean(String name)* @Date 2023/4/1 20:47*/
public class ContainerSingleton {private ContainerSingleton() {};private static Map<String, Object> ioc = new ConcurrentHashMap<>();public static Object getInstance(String className) {Object instance = null;if (!ioc.containsKey(className)){try {instance = Class.forName(className).newInstance();ioc.put(className,instance);} catch (Exception e) {throw new RuntimeException(e);}}else{instance= ioc.get(className);}return instance;}public static void main(String[] args) {Object instance1 = ContainerSingleton.getInstance("com.lc.singleton.register.User");Object instance2 = ContainerSingleton.getInstance("com.lc.singleton.register.User");System.out.println(instance1==instance2);}
}

注册式-枚举单例

package com.lc.singleton.register;/*** @Author lc* @description:注册式-枚举* * 枚举式单例*  * 优点:线程安全,不能被反射破坏*  * 缺点:不适用大批量单例对象,浪费资源* @Date 2023/4/1 20:24*/
public enum EnunSingleton {INSTANCE;private Object data;public Object getData() {return data;}public void setData(Object data) {this.data = data;}public static EnunSingleton getInstance() {return INSTANCE;}public static void main(String[] args) {EnunSingleton.getInstance().setData("你好");}
}

ThreadLocal 线程内部

package com.lc.singleton;/*** @Author lc* @description:  ThreadLocal 线程内部* @Date 2023/4/1 21:35*/
public class ThreadLocalSingleton {private static final ThreadLocal<ThreadLocalSingleton> THREAD_LOCAL_SINGLETON_THREAD_LOCAL =new ThreadLocal<ThreadLocalSingleton>() {@Overrideprotected ThreadLocalSingleton initialValue() {return new ThreadLocalSingleton();}};private ThreadLocalSingleton() {};public static ThreadLocalSingleton getInstance() {return THREAD_LOCAL_SINGLETON_THREAD_LOCAL.get();}public static void main(String[] args) {System.out.println(ThreadLocalSingleton.getInstance());System.out.println(ThreadLocalSingleton.getInstance());}
}

实现线程安全的单例模式

● 在多线程环境中,不要使用简单的懒加载方式(只在getInstance()方法内部使用synchronized),因为这种方式在每次调用getInstance()时都会进行同步,性能较差。
● 使用双重检查锁定或静态内部类方式时,要注意构造函数不要有复杂的逻辑,以避免指令重排导致的问题。虽然使用volatile可以解决这个问题,但最好保持构造函数的简单性。
● 如果单例需要被序列化,需要增加防止反序列化的机制,例如实现readResolve()方法。
通常推荐使用枚举或静态内部类的方式来实现线程安全的单例,因为它们既简单又安全。

实现线程安全的单例模式,有多种方式可以确保在并发环境下单例的唯一性。以下是一些常见的方法:

1. 饿汉式(静态初始化)

public class Singleton {  private static final Singleton INSTANCE = new Singleton();  private Singleton() {  // 私有构造方法,防止外部通过 new Singleton() 创建实例  }  public static Singleton getInstance() {  return INSTANCE;  }  
}

这种方式在类加载时就完成了初始化,所以天生就是线程安全的。
2. 懒汉式(双重检查锁定)

public class Singleton {  private volatile static Singleton instance;  private Singleton() {  // 私有构造方法,防止外部通过 new Singleton() 创建实例  }  public static Singleton getInstance() {  if (instance == null) { // 第一次检查实例是否存在,如果不存在才进入下面的同步块  synchronized (Singleton.class) {  if (instance == null) { // 第二次检查实例是否存在,如果不存在才创建实例  instance = new Singleton();  }  }  }  return instance;  }  
}

双重检查锁定是一种延迟加载技术,避免了饿汉式在类加载时就完成初始化的开销。同时,由于使用了volatile关键字和双重检查,保证了线程安全。
3. 静态内部类

public class Singleton {  private Singleton() {  // 私有构造方法,防止外部通过 new Singleton() 创建实例  }  private static class SingletonHolder {  private static final Singleton INSTANCE = new Singleton();  }  public static Singleton getInstance() {  return SingletonHolder.INSTANCE;  }  
}

静态内部类的方式利用了 JVM 的类加载机制来保证线程安全。当SingletonHolder类被加载时,会初始化其静态变量INSTANCE,由于 JVM 在类加载时是线程安全的,因此这种方式也是线程安全的。
4. 枚举(推荐)

public enum Singleton {  INSTANCE;  public void whateverMethod() {  // 方法体  }  
}

在 Java 中,枚举是线程安全的,并且只会加载一次。因此,使用枚举来实现单例是最简单且最安全的方式。
5. 使用 java.util.concurrent.atomic.AtomicReference

import java.util.concurrent.atomic.AtomicReference;  public class Singleton {  private static final AtomicReference<Singleton> INSTANCE_REF = new AtomicReference<>();  private Singleton() {  // 私有构造方法,防止外部通过 new Singleton() 创建实例  }  public static Singleton getInstance() {  for (;;) {  Singleton current = INSTANCE_REF.get();  if (current != null) {  return current;  }  Singleton newInstance = new Singleton();  if (INSTANCE_REF.compareAndSet(null, newInstance)) {  return newInstance;  }  // 如果当前实例已经被其他线程初始化,则丢弃新创建的实例,并重试  }  }  
}

使用AtomicReference和CAS(Compare-and-Swap)操作可以确保线程安全地实现单例。这种方式比双重检查锁定更为复杂,但在高并发场景下可能具有更好的性能。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/bicheng/3652.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Kubernetes:云原生时代的核心引擎

文章目录 一、Kubernetes简介&#xff1a;引领云原生潮流二、K8s的核心特性&#xff1a;自动化与智能化三、K8s的实践应用&#xff1a;打造高效云原生应用架构四、K8s的挑战与应对&#xff1a;安全与性能并重五、K8s的未来展望&#xff1a;无限可能与挑战并存《Kubernetes快速进…

【windows-搭建Ubuntu22LTS】

一、环境要求 1. windows版本要求 至少Windows 10 2020年5月(2004) 版, Windows 10 2019年5月(1903) 版&#xff0c;或者 Windows 10 2019年11月(1909) 版 2. 控制面板开启相关的程序(需要重启) 二、Microsoft store安装unbuntu 下载后直接运行&#xff08;稍微等会&#…

从开发角度理解漏洞成因(01)

文章目录 PHP开发漏洞环境&#xff08;SQL注入&#xff09;生成前端代码生成后端代码数据库写功能调试功能 测试SQL注入漏洞字符型注入布尔盲注 PHP开发漏洞环境&#xff08;SQL注入&#xff09; 持续更新中… 文章中代码资源已上传资源&#xff0c;如需要打包好的请点击PHPM…

亿道三防onerugged|工业车载电脑在港口正面吊上的应用

港口正面吊是港口作业中至关重要的设备&#xff0c;它承担着装卸集装箱等重要任务。作为专业人员&#xff0c;我深知港口作业的复杂性和挑战性。在这方面&#xff0c;亿道三防onerugged系列的工业车载电脑为港口正面吊的应用提供了一种创新的解决方案。 首先&#xff0c;工业车…

milvus对象存储和消息中间件的工厂设计模式分析

milvus对象存储和消息中间件的工厂设计模式分析 需求 根据参数设置创建mq和storage mq有kafka,pulsar storage有local,minio,remote 配置文件 根据配置文件选择初始化mq和存储: mq:type: pulsarcommon:storageType: minio对于这种类型一个是mq&#xff0c;一个是存储&…

理解与解决BouncyCastle库中“ASN1Primitive overrides final method equals”异常

理解与解决BouncyCastle库中“ASN1Primitive overrides final method equals”异常 引言错误原因分析应用场景及解决方案示例示例一&#xff1a;不同版本间的兼容性问题示例二&#xff1a;库之间的相互影响示例三&#xff1a;JDK版本适配问题 结论 引言 在Java开发中&#xff0…

springboot+vue新疆肉牛智慧牧场养殖系统

系统涉及的对象是奶牛。 系统使用员工有管理员和普通员工。 管理员有修改的权限&#xff0c;普通员工没有。 系统需要包含奶牛的编号&#xff0c;种类&#xff0c;体重&#xff0c;健康情况、生长情况、牛奶产量&#xff0c;以及上次更新数据时间等信息&#xff0c;管理员可以对…

关于权限的设计

首先系统权限&#xff0c;每个账号登录后&#xff0c;都需要知道这个账号允许访问哪些api&#xff0c;哪些数据权限&#xff08;一般是指其他账号的一些数据&#xff09; 这里就需要通过角色来关联。 --1.角色绑定菜单&#xff0c;每个菜单设计的时候包含了这个菜单会用到的所…

设计模式:合成复用原则(Composite Reuse Principle,CRP)介绍

合成复用原则&#xff08;Composite Reuse Principle&#xff0c;CRP&#xff09;是面向对象设计原则之一&#xff0c;它强调通过组合已有的对象来实现新的功能&#xff0c;而不是通过继承已有的类来实现。合成复用原则的核心思想是尽量使用对象组合而不是类继承&#xff0c;从…

Hive官方文档 join table 总结

Hive官方文档 join table 总结 join_table:table_reference [INNER] JOIN table_factor [join_condition]| table_reference {LEFT|RIGHT|FULL} [OUTER] JOIN table_reference join_condition| table_reference LEFT SEMI JOIN table_reference join_condition| table_referen…

C语言中定义和声明的区别

定义: 编译器在创建一个对象时, 为该对象申请开辟了内存空间, 这个空间的名字就是变量或者对象名, 同一个变量名在某个区域只能定义一次, 重复定义会出现错误 声明有两种作用: 1. 告诉编译器, 这个变量或者函数, 我已经定义了 (开辟了空间了), 但是在别的地方, 我先说明一下…

HarmonyOS 实战开发-使用canvas实现图表系列之折线图

一、功能结构 实现一个公共组件的时候&#xff0c;首先分析一下大概的实现结构以及开发思路&#xff0c;方便我们少走弯路&#xff0c;也可以使组件更加容易拓展&#xff0c;维护性更强。然后我会把功能逐个拆开来讲&#xff0c;这样大家才能学习到更详细的内容。下面简单阐述…

C# 调整图像的亮度简单示例

操作顺序&#xff1a; 首先加载要调整亮度的图像。 然后通过循环遍历图像的像素&#xff0c;并根据需要增加像素的亮度值。 最后&#xff0c;将调整后的图像保存回原始文件。 代码如下&#xff1a; using System; using System.Drawing.Imaging;class Program {static void…

【Linux系统编程】基础指令(二)

&#x1f49e;&#x1f49e; 前言 hello hello~ &#xff0c;这里是大耳朵土土垚~&#x1f496;&#x1f496; &#xff0c;欢迎大家点赞&#x1f973;&#x1f973;关注&#x1f4a5;&#x1f4a5;收藏&#x1f339;&#x1f339;&#x1f339; &#x1f4a5;个人主页&#x…

架构师系列- 定时任务(二)- Quartz框架

quartz特点 Quartz是一个优秀的任务调度框架&#xff0c; 具有以下特点 强大的调度功能&#xff0c;例如支持丰富多样的调度方法&#xff0c;可以满足各种常规及特殊需求&#xff1b;负载均衡高可用 quartz 架构体系 Quartz 设计有四个核心类&#xff0c;分别是Scheduler(调度…

一个简单的java递归下降语法分析器例子

import parser.Parser; import parser.RecursiveDescentParser;import java.util.ArrayList; import java.util.Arrays; import java.util.List;public class Main {public static void main(String[] args) {// 关键词List<String> keyList new ArrayList<>(Arra…

NXP i.MX8系列平台开发讲解 - 3.10 Linux PCIe资源分配与访问(二)

目录 1. PCIe BFD 2. PCIe 配置空间 2.1 PCIe 配置空间访问 PCIe I/O访问方法 PCIe MMIO访问方法 3. PCIe BAR相关 4. PCIe Capbility 5. PCIe 操作 本文将重点讲解PCIe的资源访问相关内容&#xff0c;对于PCIe资源访问是从Host 端老看可以对PCIe进行配置与访问的资源主…

【opencv 加速推理】如何安装 支持cuda的opencv 包 用于截帧加速

要在支持CUDA的系统上安装OpenCV&#xff0c;您可以使用pip来安装支持CUDA的OpenCV版本。OpenCV支持CUDA加速&#xff0c;但需要安装额外的库&#xff0c;如cuDNN和NVIDIA CUDA Toolkit。以下是一般步骤&#xff1a; 安装NVIDIA CUDA Toolkit: 首先&#xff0c;您需要安装NVID…

深度学习基础之《TensorFlow框架(12)—图片数据》

一、图像基本知识 1、如何转换图片文件 回忆&#xff1a;之前我们在特征抽取中讲过如何将文本处理成数据 思考&#xff1a;如何将图片文件转换成机器学习算法能够处理的数据&#xff1f; 我们经常接触到的图片有两种&#xff0c;一种是黑白图片&#xff08;灰度图&#xff09;…

网站被SmartScreen标记为不安全怎么办?

在互联网时代&#xff0c;网站的安全性和可信度是用户选择是否继续访问的重要因素之一&#xff0c;然而&#xff0c;网站运营者偶尔会发现使用Edge浏览器访问网站时&#xff0c;会出现Microsoft Defender SmartScreen&#xff08;以下简称SmartScreen&#xff09;提示网站不安全…