设计模式 | 单例模式

1.核心作用

保证一个类只有一个实例,提供一个访问该实例的全局访问点。

2.常见场景

在这里插入图片描述

3.单例模式的优点

在这里插入图片描述

4.常见的五种单例模式实现方式

在这里插入图片描述

4.1 饿汉式

步骤:

  1. 私有化构造器
  2. 类初始化时,立即加载对象【不涉及线程安全问题】
  3. 提供获取该对象的方法【没有Synchronized,效率高】


// 恶汉式

public class SingletonDemo1 {
    private SingletonDemo1(){};

    private static SingletonDemo1 instance= new SingletonDemo1();

    public static SingletonDemo1 GetInstance(){
        return instance;
    }
}


class SingletonDemo1Test {
    public static void main(String[] args) {
        SingletonDemo1 instance1 = SingletonDemo1.GetInstance();
        SingletonDemo1 instance2 = SingletonDemo1.GetInstance();

        boolean b = instance1 == instance2;
        System.out.println(b);
        //输出:true
    }
}

优点: 线程安全,调用效率高

缺点: 不能延时加载,如果长时间不用,会造成资源浪费

解决方案: 懒汉式

4.2 懒汉式

步骤:

  1. 私有化构造器
  2. 类初始化时,不立即加载对象
  3. 提供获取该对象的方法,在该方法上用Synchronized关键字,解决多线程冲突问题
//懒汉式
public class SingletonDemo2 {
    //1. 私有化构造器
    private  SingletonDemo2(){};

    //2. 类初始化时,不立即加载对象
    private static SingletonDemo2 instance;

    //3. 提供获取该对象的方法,在该方法上用Synchronized关键字,解决多线程冲突问题
    public static synchronized SingletonDemo2 getInstance(){
        if(instance!=null){
                return new SingletonDemo2();
            }
            return instance;
    }

}

class SingletonDemo2Test {
    public static void main(String[] args) {
        SingletonDemo2 instance1 = SingletonDemo2.getInstance();
        SingletonDemo2 instance2 = SingletonDemo2.getInstance();

        boolean b = instance1 == instance2;
        System.out.println(b);
        //输出:true
    }
}

优点: 延时加载,即在调用方法时产生对象,解决恶汉式资源浪费的问题。
缺点: Synchronized关键字会使代码效率变低
解决方案: DCL懒汉式,即双重检测锁模式,解决代码效率问题。

4.3 DCL懒汉式(双重检测锁模式)

步骤:

  1. 私有化构造器
  2. 类初始化时,不立即加载对象
  3. 提供获取该对象的方法,用volatile关键字避免指令重排带来的问题;缩小Synchronized代码块范围,解决代码效率问题。
//DCL懒汉式(双重检测锁模式)
public class SingletonDemo3 {
    //私有化构造器
    private SingletonDemo3() {

    }

    //只提供一个实例,并不创建对象
    //使用避免指令重排带来的线程安全问题
    //volatile:对于同一个变量,在一个线程中值发生了改变,则在另一个线程中立即生效,可以大幅度避免下面的问题,不排除极端情况
    private static volatile SingletonDemo3 instance;

    //提供公共的获取方法,因为不是在类加载时就创建对象,因此存在线程安全问题,使用同步代码块提高效率
    //现在不需要对整个方法进行同步,缩小了锁的范围,只有第一次会进入创建对象的方法,提高了效率
    //当第一个线程执行到创建对象的方法时,但还未出方法返回,此时第二个线程进入,发现instance不为空,但第一个线程此时还未出去,可能发送意想不到的安全问题
    public static SingletonDemo3 getInstance() {
        if (instance == null) {
            synchronized (SingletonDemo3.class) {
                if (instance == null) {
                    instance = new SingletonDemo3();
                }
            }
        }
        return instance;
    }
}

//测试
class SingletonDemo3Test {
    public static void main(String[] args) {
        SingletonDemo3 instance = SingletonDemo3.getInstance();
        SingletonDemo3 instance1 = SingletonDemo3.getInstance();
        System.out.println(instance == instance1); //输出true
    }
}

问题:为什么需要两次判断if(singleTon==null)?

分析

  • 第一次校验:由于单例模式只需要创建一次实例,如果后面再次调用getInstance方法时,则直接返回之前创建的实例,因此大部分时间不需要执行同步方法里面的代码,大大提高了性能。如果不加第一次校验的话,那跟上面的懒汉模式没什么区别,每次都要去竞争锁。

  • 第二次校验:如果没有第二次校验,假设线程t1执行了第一次校验后,判断为null,这时t2也获取了CPU执行权,也执行了第一次校验,判断也为null。接下来t2获得锁,创建实例。这时t1又获得CPU执行权,由于之前已经进行了第一次校验,结果为null(不会再次判断),获得锁后,直接创建实例。结果就会导致创建多个实例。所以需要在同步代码里面进行第二次校验,如果实例为空,则进行创建。

  • 需要注意的是, private static volatile SingletonDemo3 instance;需要加volatile关键字,否则会出现错误问题的原因在于JVM指令重排优化的存在。在某个线程创建单例对象时,在构造方法被调用之前,就为该对象分配了内存空间并将对象的字段设置为默认值。此时就可以将分配的内存地址赋值给instance字段了,然而该对象可能还没有初始化。若紧接着另外一个线程来调用getInstance,取到的就是状态不正确的对象,程序就会出错。

4.4 静态内部类

步骤:

  1. 私有化构造器
  2. 使用静态内部类(实现延迟加载)
  3. 提供获取该对象的方法

静态内部类:同样也是利用了类的加载机制,它与饿汉模式不同的是,它是在内部类里面去创建对象实例。这样的话,只要应用中不使用内部类,JVM就不会去加载这个单例类,也就不会创建单例对象,从而实现懒汉式的延迟加载。也就是说这种方式可以同时保证延迟加载和线程安全。

public class SingletonDemo4 {
    private SingletonDemo4(){};

    private static  class InnerClass{
        private static final SingletonDemo4 instance=new SingletonDemo4();
    }

    public static SingletonDemo4 getInstance(){
        return InnerClass.instance;
    }
}

优点: 线程安全,调用效率高,可延时加载

缺点: 反射机制可以轻易破坏上述单例

class SingletonDemo4Test {
    public static void main(String[] args) throws Exception {
        SingletonDemo4 instance1 = SingletonDemo4.getInstance();
        
        Constructor<SingletonDemo4> declaredConstructor = SingletonDemo4.class.getDeclaredConstructor();
        declaredConstructor.setAccessible(true);
        SingletonDemo4 instance2 = declaredConstructor.newInstance();

        System.out.println(instance1==instance2);
        //输出:false
    }
}

由上面的例子可以得出,反射是可以破坏以上四种的单例模式(这里不一一演示)

那怎样才能解决这个问题呢,我们来看一下反射创建对象的newInstance()方法:

在这里插入图片描述
从源码中可以看出,当反射遇到枚举时直接抛出异常,因此,枚举是创建单例的不二之选

4.5 枚举

原理:在反射的源码中,我们发现,反射天然屏蔽枚举类型,所以枚举的对象纯天然是单例的。

public enum  SingletonDemo5 {
    INSTANCE;

    public SingletonDemo5 getInstance(){
        return INSTANCE;
    }
}
class SingletonDemo5Test {
    public static void main(String[] args) {
        SingletonDemo5 instance1 = SingletonDemo5.INSTANCE;
        SingletonDemo5 instance2 = SingletonDemo5.INSTANCE;

        boolean b = instance1 == instance2;
        System.out.println(b);
        //输出:true
    }
}

优点: 线程安全,调用效率高,

缺点: 不能延时加载

5.五种实现单例模式的方式的对比

饿汉式:线程安全(不排除反射),调用效率高,不能延时加载
懒汉式:线程安全(不排除反射),调用效率不高,可以延时加载
DCL懒汉式:由于JVM底层模型原因,偶尔出现问题,不建议使用
静态内部类式:线程安全(不排除反射),调用效率高,可以延时加载
枚举单例:线程安全,调用效率高,不能延时加载,避免反射带来的问题。

参考资料:单例模式的五种实现方式

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
该资源内项目源码是个人的课程设计、毕业设计,代码都测试ok,都是运行成功后才上传资源,答辩评审平均分达到96分,放心下载使用! ## 项目备注 1、该资源内项目代码都经过测试运行成功,功能ok的情况下才上传的,请放心下载使用! 2、本项目适合计算机相关专业(如计科、人工智能、通信工程、自动化、电子信息等)的在校学生、老师或者企业员工下载学习,也适合小白学习进阶,当然也可作为毕设项目、课程设计、作业、项目初期立项演示等。 3、如果基础还行,也可在此代码基础上进行修改,以实现其他功能,也可用于毕设、课设、作业等。 下载后请首先打开README.md文件(如有),仅供学习参考, 切勿用于商业用途。 该资源内项目源码是个人的课程设计,代码都测试ok,都是运行成功后才上传资源,答辩评审平均分达到96分,放心下载使用! ## 项目备注 1、该资源内项目代码都经过测试运行成功,功能ok的情况下才上传的,请放心下载使用! 2、本项目适合计算机相关专业(如计科、人工智能、通信工程、自动化、电子信息等)的在校学生、老师或者企业员工下载学习,也适合小白学习进阶,当然也可作为毕设项目、课程设计、作业、项目初期立项演示等。 3、如果基础还行,也可在此代码基础上进行修改,以实现其他功能,也可用于毕设、课设、作业等。 下载后请首先打开README.md文件(如有),仅供学习参考, 切勿用于商业用途。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值