Java设计模式-创建者模式-单例模式

单例模式

单例模式

解释:一个类只能有一个实例

单例模式可以分为两种 饿汉式 和 懒汉式

饿汉式

也被称为预加载,即 在加载类的时候,就将实例创建出来,加载到内存,不管之后会不会使用这个实例

主打一个饥不择食,体现了贪心的思想。

java 复制代码
public class HungrySingleton {
    //方式1:静态变量
    private static HungrySingleton instance = new HungrySingleton();

    //方式2:静态代码块
    /*static {
        instance = new HungrySingleton();
    }*/
    //方式3:枚举,可以看 EnumSingleton.class

    public static HungrySingleton getInstance(){
        return instance;
    }
}

很明显,我们还没有使用该对象,就已经加载到了内存,浪费内存

但是,同时,因为只有一此创建对象,所以饿汉式是线程安全的

懒汉式

也成为懒加载 ,即:只有在使用该类时才创建需要的对象 。

我很懒,你不用我,我就不创建对象

一般有四种实现方式

  1. 简单懒汉式,不能用,线程不安全
  2. Synchronized 同步方法,一般不用,线程安全,但锁粒度太大,效率较低
  3. 双重检查锁,可以用 ,注意使用volatile 关键字保证单例对象的原子性
  4. 静态内部类,推荐使用
java 复制代码
public class LazySingleton {
    
    private volatile static LazySingleton instance;

    /**
     * 简单懒汉式
     * 问题:线程不安全,一般不用
     * @return
     */
    public static LazySingleton getInstance01() {
        if(null == instance){
            instance = new LazySingleton();
        }
        return instance;
    }

    /**
     * synchronized
     * 解决了线程安全问题,但效率低,一般不用
     * @return
     */
    public synchronized static LazySingleton getInstance02() {
        if(null == instance){
            instance = new LazySingleton();
        }
        return instance;
    }

    /**
     * 双重检查锁模式
     * 降低锁的粒度,只锁创建对象的代码块
     * 需要增加 volatile 来保证原子性,防止jvm指令重排,但同时屏蔽了JVM的一些代码优化
     * @return
     */
    public static LazySingleton getInstance03() {
        if(null == instance){
            synchronized (LazySingleton.class){
                if(null == instance){//防止重复创建对象
                    instance = new LazySingleton();
                }
            }
        }
        return instance;
    }

    /**
     * 静态内部类方式
     * 只有在使用时才会创建静态内部类,推荐使用
     * @return
     */
    public static LazySingleton getInstance04() {
        return SingletonHolder.INSTANCE;
    }

    private static class SingletonHolder{
        private static final LazySingleton INSTANCE = new LazySingleton();
    }
}

这里解释下 为什么 双重检查锁要 使用 volatile 关键字 修饰单例对象

if判断以及其内存执行代码是非原子性的。其次,new LazySingleton()无法保证执行的顺序性。

显然,不满足原子性或者顺序性,线程肯定是不安全的。

下面主要讲一下 new LazySingleton() 为什么不能保证顺序性。

设想一下,创建一个对象,应该分为几步?

答案是三步,如下:

memory=allocate();//1:初始化内存空间

ctorInstance(memory);//2:初始化对象

instance=memory();//3:设置instance指向刚分配的内存地址

jvm为了提高程序执行性能,会对没有依赖关系的代码进行重排序 ,也就是说上面2和3行代码可能被重新排序。

我用两个线程举例:

时间片 线程A 线程B
t1 初始化内存空间
t2 设置instance指向刚分配的内存地址
t3 判断instance 是否为空
t4 由于instanc不为空,获取到一个空的实例化对象(线程不安全)
t5 初始化对象

volatile 关键字就是强制要求jvm 不进行指令重排,按顺序执行,当然也有一定的性能损失

volatile 其他相关知识 可以参考以下文章:
https://blog.csdn.net/m0_50370837/article/details/124380385

相关推荐
学长毕设44 分钟前
基于springboot的食品仓库管理系统
java·spring boot·后端
步行cgn1 小时前
FormHttpMessageConverter 详解:Spring MVC 中表单数据的读写转换器
java·spring·mvc
龙亘川1 小时前
构建城市公共安全数字底座:亘川智城智慧安防系统全维度解读
java·大数据·开发语言·人工智能·信息可视化
金銀銅鐵1 小时前
[Java] 用 CLI 展示 EnumMap 增删键值对时的内部变化
java·源码·ai编程
秋天的怀念_David2 小时前
第二十二夜 · 数字迷雾
java·程序人生·算法
Raas1002 小时前
AI网关部署方案?从部署到上线全流程
java·开发语言·人工智能·gateway·ai网关·企业级ai网关·企业级ai产品
niucloud-admin3 小时前
NIUCLOUD PHP SAAS上门家政使用文档:城市管理
java·前端·php
霸道流氓气质3 小时前
智能客服组件:分层意图识别、RAG Advisor 与人工转接状态机的 Vue3 + Spring AI 生产级实战
java·人工智能·spring
captain3764 小时前
MyBatis操作数据库(入门2)
java·数据库·spring boot·后端·spring·mybatis
心中有你02144 小时前
Java实现Dijkstra最短路径算法(邻接表 + 优先队列)
java·数据库·算法