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

相关推荐
chuanauc2 分钟前
Kubernets K8s 学习
java·学习·kubernetes
一头生产的驴18 分钟前
java整合itext pdf实现自定义PDF文件格式导出
java·spring boot·pdf·itextpdf
YuTaoShao25 分钟前
【LeetCode 热题 100】73. 矩阵置零——(解法二)空间复杂度 O(1)
java·算法·leetcode·矩阵
zzywxc78728 分钟前
AI 正在深度重构软件开发的底层逻辑和全生命周期,从技术演进、流程重构和未来趋势三个维度进行系统性分析
java·大数据·开发语言·人工智能·spring
YuTaoShao3 小时前
【LeetCode 热题 100】56. 合并区间——排序+遍历
java·算法·leetcode·职场和发展
程序员张33 小时前
SpringBoot计时一次请求耗时
java·spring boot·后端
llwszx6 小时前
深入理解Java锁原理(一):偏向锁的设计原理与性能优化
java·spring··偏向锁
Small black human6 小时前
设计模式-应用分层
设计模式
云泽野6 小时前
【Java|集合类】list遍历的6种方式
java·python·list
二进制person7 小时前
Java SE--方法的使用
java·开发语言·算法