线程安全的单例模式--“饿汉“,“懒汉“

2024-07-19 08:24:45 浏览数 (2)

1.什么是设计模式?

设计模式好⽐象棋中的"棋谱".红⽅当头炮,⿊⽅⻢来跳.针对红⽅的⼀些⾛法,⿊⽅应招的时候有⼀ 些固定的套路.按照套路来⾛局势就不会吃亏. 软件开发中也有很多常⻅的"问题场景".针对这些问题场景.

单例模式能保证某个类在程序中只存在唯⼀⼀份实例,⽽不会创建出多个实例.

2.饿汉模式

代码语言:javascript复制
class Singleton {
    private static Singleton instance = new Singleton();
    //在这个类被加载的时候,就会初始化这个 静态变量

    public static Singleton getInstance() {
        return instance;
    }

    private Singleton(){}
    //后续再别的代码中,尝试new  Singleton,就会直接编译报错
}

对于饿汉来说,getInstance 直接返回instance实例, 这个本质上就是"读操作".所以即使是多个线程同时读取一个变量,线程也是安全的 

3.懒汉模式

代码语言:javascript复制
class SingletonLazy {
    private static SingletonLazy instance = null;
    //这个指引指向唯一实例,想爱你初始化为空,而不是立即创建实例

    public static SingletonLazy getInstance() {
        if (instance == null) {
            instance = new SingletonLazy();
        }
        return instance;
    }
    private SingletonLazy() {}
}

如果是首次调用getInstance, 此时引用为null,就会进入if语句,从而创建实例 如果是后续再次调用getInstance,由于instance已经不再是null,所以直接返回创建好的引用 这样设定,仍然可以保证该类是唯一实例,与此同时,创建实例的时机就不是程序驱动时,而是第一次调用getInstance的时候了

 4.懒汉模式(多线程版)

上⾯的懒汉模式的实现是线程不安全的.   线程安全问题发⽣在⾸次创建实例时. 如果在多个线程中同时调⽤ getInstance ⽅法, 就可能导致创建 出多个实例. ⼀旦实例已经创建好了, 后⾯再多线程环境调⽤getInstance就不再有线程安全问题了(不再修改  instance 了)  而加上 synchronized 可以改善这⾥的线程安全问题.

代码语言:javascript复制
class SingletonLazy {
    private static SingletonLazy instance = null;
    //这个指引指向唯一实例,想爱你初始化为空,而不是立即创建实例
    
    private static Object locker = new Object();
    public static SingletonLazy getInstance() {
        synchronized (locker) {
            if (instance == null) {
                instance = new SingletonLazy();
            }
        }
      
        return instance;
    }
    private SingletonLazy() {}
}

5.懒汉模式(多线程)(改进)

加锁 / 解锁是⼀件开销⽐较⾼的事情. ⽽懒汉模式的线程不安全只是发⽣在⾸次创建实例的时候. 因此后续使⽤的时候, 不必再进⾏加锁了. 所以这个时候可以在家一个 if 判定是否要加锁.

代码语言:javascript复制
 private static Object locker = new Object();
    public static SingletonLazy getInstance() {
        if (instance == null) {  //第一层 if 是判定是否要加锁
            synchronized (locker) {
                if (instance == null) {// 第二层 if 是判定是否要创建对象
                    instance = new SingletonLazy();
                }
            }
        }
        return instance;
    }

 这个代码还是有些问题 指令重排序 , 是编译器优化的一种方式 , 也是引起线程安全的问题. 指令重排序是指调整原有的代码执行顺序,保证逻辑不变的前提下,提高程序效率

代码语言:javascript复制
 instance = new SingletonLazy();

这行代码可以拆成三大步骤 1.申请一段内存空间 2.在这个内存上调用构造方法,创建出这个实例 3.把这个内存地址赋值给 instance 引用变量

 所以,解决上述问题 需要用到 volatile,它有两个功能: 1.保证内存可见性,每次访问变量必须都要重新读取内存,而不是直接优化到寄存器 / 缓存中 2.禁止指令重排序, volatile 修饰的变量的读写操作相关指令,不能被重排序的 所以最终代码为下

代码语言:javascript复制
class SingletonLazy {
    private volatile static SingletonLazy instance = null;
    //这个指引指向唯一实例,想爱你初始化为空,而不是立即创建实例

    private static Object locker = new Object();
    public static SingletonLazy getInstance() {
        if (instance == null) {  //第一层 if 是判定是否要加锁
            synchronized (locker) {
                if (instance == null) {// 第二层 if 是判定是否要创建对象
                    instance = new SingletonLazy();
                }
            }
        }
        return instance;
    }
    private SingletonLazy() {}
}

0 人点赞