常用设计模式——5种方式实现单例模式

1 单例模式(静态内部类方式实现 懒汉)

class Singleton{
    // 构造方法私有化,防止被其他类创建对象
    private Singleton(){}

    public static class LazyLoader{
        // 静态内部类中创建 静态的Singleton对象
        private static final Singleton INSTANCE = new Singleton();
    }

    // 对外只暴露一个方法,用于获取该单例对象
    public static Singleton getInstance(){
        // 1、外部类class Singleton初始化,不会触发静态内部类static class LazyLoader的初始化
        // 2、只有 当 其他类调用getInstance方法获取INSTANCE时
        //  ,才会创建Singleton实例,触发初始化————>【懒汉】
        //  解释: 由于INSTANCE是 静态常量中的引用类型
        //  因此在该对象创建前,才会触发该类(静态内部类)LazyLoader的加载和初始化
        //  而静态内部类LazyLoader在加载时,是线程安全的
        //  ——>也就保证了Singleton对象被创建的时候,不会出现线程安全问题
        return LazyLoader.INSTANCE;
    }
}

2 双重检查锁Double Check (懒汉式)


/**
 * @description: 双重检查锁 创建单例模式
        * @param: null
        * @return:
        * @author HUANGXUAN
        * @date: 2021/7/20 21:23
 */
class DoubleCheckSingleton{
    //  私有构造方法,防止其他类创建对象

    private DoubleCheckSingleton() {}

    // 为何使用 volatile关键字修饰? ——>
    // 1、禁用CPU指令重排序,防止以下代码编译后的执行顺序变化
    // 2、保证共享变量的可见性——>缓存一致性协议——>开启总线嗅探机制

    private static volatile DoubleCheckSingleton SINGLETON;

    // 双重检查锁的精华部分:
    //  当其他类调用该类的getInstance方法时,会发生以下情况:
    
    private static DoubleCheckSingleton getInstance(){
        // 第一道关卡  用于优化执行效率——>
        // 因为如果synchronized加在getInstance方法上
        // 当多个线程同时调用getInstance方法时,一个线程拿到锁,会让其他线程陷入阻塞,降低效率

        if (SINGLETON == null){
            synchronized (DoubleCheckSingleton.class){
                if (SINGLETON == null){
                    SINGLETON = new DoubleCheckSingleton();
                }
            }
        }
        return SINGLETON;
    }
}

DoubleCheck实现过程分析:

T1线程调用时,发现SINGLETON == null,于是进入第一道关卡,
顺理成章拿到锁,进入第二道关卡(SINGLETON == null),
开始进入创建SINGLETON对象的时刻(加载 连接 初始化...)--------------->  (一)

如上,在T1线程过完第一道关卡,拿到锁,把创建对象的代码块锁住时,
如果T2线程是紧跟着T1线程调用getInstance的话,它也会进入第一道关卡
但由于创建对象的代码块被锁住了,于是他只能进入阻塞状态
接着当T1线程创建完对象出来后,return SINGLETON;
T2线程被唤醒,拿到锁,进行第二道判断SINGLETON == null—> 发现SINGLETON对象不为空了
所以T2线程直接返回 T1线程创建的SINGLETON对象,这就保证了【单例模式】

如果Instance 不加volatile 则会出现——> 【返回半初始化对象】的情况:

有一种情况是:
第一个调用getInstance方法的T1线程在【刚进入同步代码块,执行对象创建时,即 (一)时刻】
由于对象创建是需要经过(加载——>连接——>调用构造方法并初始化)三个步骤,
然后再将创建好的对象的引用地址,赋值给SINGLETON这个变量。此时SINGLETON才 !=null

【由于没加volatile 去禁用CPU指令重排序!
因此赋值引用地址给SINGLETON这个变量的操作:putstatic
【可能会由于重排序,而提前到 调用构造方法和初始化:invokespecial之前!】
因此此时 if判断出SINGLETON确实也不为空了。他就会认为对象已经创建完成
而后:后面的return实例的操作就会返回一个半初始化的对象】

!!! 那么问题来了??直接return? 可是这个时候,SINGLETON变量指向的对象还没有初始化完成!
也就是说,这个对象它才生出来一半,还有一半没生出来——> 即【返回一个半初始化的对象】

volatile 是如何解决问题的?

加了volatile 则可以解决这个问题。
因为Instance 在if(Instance == null)这个读操作中,volatile 会加入一个读屏障!
保证读屏障之后的每次共享变量读取,都会去主内存中读取!并且防止读屏障以后的代码被重排序到它前面去!

而在Instance = new Singleton 这个创建对象并赋值的写操作中,则会加入写屏障。
防止写屏障之前的代码,被重排序到后面去!

3 饿汉模式

public final class Singleton implements Serializable {
 private Singleton() {}
 这样初始化是否能保证单例对象创建时的线程安全?
 // 是线程安全的。因为static修饰的变量赋值操作,会被整合成一个cinit方法。在初始化阶段执行。
 //此时是线程安全的
 private static final Singleton INSTANCE = new Singleton();
 为什么提供静态方法而不是直接将 INSTANCE 设置为 public, 说出你知道的理由
 // 可以用于实现懒惰的初始化,预留给后期进行修改扩展,还可以在创建单例时有更多控制
 public static Singleton getInstance() {
 return INSTANCE;
 }
 public Object readResolve() {
 return INSTANCE;
 }
}
为什么加 final?

防止被继承重写破坏

为什么设置为私有? 是否能防止反射创建新的实例?

防止其他类调用他的构造方法。反射不能防止创建新的实例。反射可以通过暴力破解方式,
拿到私有的构造方法

怎么防止反序列化破坏单例模式?

该单例类如果implements Serializable。
当使用反序列化创建对象时,会调用readResovle方法,返回的将不是我们要的单例对象。
因此我们直接重写该方法,返回需要的单例对象
public Object readResovle(){
return INSTANCE;
}
避免反序列化获取对象时,使用他默认的方法

4 枚举方式——饿汉模式

问题1:枚举单例是如何限制实例个数的
//  反编译得出:该INSTANCE就是枚举类种的一个静态成员变量。 static enum INSTANCE;
问题2:枚举单例在创建时是否有并发问题
//   不会有并发线程安全问题。因为他也是static成员变量
问题3:枚举单例能否被反射破坏单例
//  【好处】  不能用反射破坏单例
问题4:枚举单例能否被反序列化破坏单例
//   枚举类默认实现序列化接口!
//   不会被反序列化破坏单例。不用自己做额外操作。
问题5:枚举单例属于懒汉式还是饿汉式
//   饿汉
问题6:枚举单例如果希望加入一些单例创建时的初始化逻辑该如何做
//   可以写一个构造方法,把初始化的逻辑加入进去即可
enum Singleton { 
 INSTANCE; 
}

5 懒汉式

public final class Singleton {
 private Singleton() { }
 private static Singleton INSTANCE = null;
 分析这里的线程安全, 并说明有什么缺点
 //  线程安全。但是效率低。 同步代码块太长了。每次调用getInstance时都会直接陷入阻塞
 public static synchronized Singleton getInstance() {
 if( INSTANCE != null ){
 return INSTANCE;
 } 
 INSTANCE = new Singleton();
 return INSTANCE;
 }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Binary H.

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值