单例模式的五种方式

根据B站尚学堂视频整理:https://www.bilibili.com/video/BV1F54y1R7L1?p=1

参考菜鸟教程:https://www.runoob.com/design-pattern/singleton-pattern.html

核心作用: 保证一个类只有一个实例,并且提供了一个访问该实例的全局访问点。

常见应用场景:

  • windows的任务管理器 就是一个单例模式。

  • winwods的Recycle Bin(回收站)也是典型的单例应用。在整个系统运行过程中,回收站一直维护者仅有的一个实例。

  • 网站的计数器,一般也是采用单例模式实现,否则难以同步。

  • 在Spring的Bean默认就是单例的,这样做的优点是Spring容器可以管理。

  • Spring MVC框架中,控制器对象也是单例。

单例模式的优点:

  • 由于单例模式只生成一个实例,减少了系统性能的开销。当一个对象的生产需要比较多的资源时,可以通过在应用启动时直接生产一个单例对象,然后永久驻留内存的方式来解决。

  • 单例模式可以在系统设置全局的访问点,优化共享资源访问,例如可以设计一个单例类,负责所有数据表的映射处理。

常见的五种单例模式:

主要

  • 懒汉式(线程安全,调用效率不高。可以延迟加载)

  • 饿汉式(线程安全,调用效率高。不能延迟加载)

其他

  • 双重检测锁式
  • 静态内部类式(线程安全,调用效率高。可以延迟加载)
  • 枚举单例(线程安全,调用效率高。不能延迟加载)

延迟加载(lazy load)也称为懒加载。
简单来说,就是只有在使用的时候,才去调用或加载。

1、懒汉式(单例对象延迟加载)

是延迟加载。

代码实现:

//懒汉式 单例模式
public class SingletonDemo2 {
    
    
    
    private static SingletonDemo2 instance;

    //构造器私有化。这样该类就不会被实例化
    private SingletonDemo2(){
    
    
    }
    
    //方法同步,调用效率低
    public static synchronized SingletonDemo2 getInstance(){
    
    
        if (instance == null){
    
    
            instance = new SingletonDemo2();
        }
        return instance;
    }
}

解释及优缺点:

代码中的getInstance方法加了synchronized 锁,是懒汉式线程安全的写法,但效率不高。

不加锁,是线程不安全方式的写法,严格意义上讲,不算单例模式。假如有两个线程A,B,同时调用getInstance方法,由于没有synchronized锁,有可能出现:线程A判断为null后被挂起,不new对象;然后执行线程B,发现也为空,new了对象;此时线程A被唤醒,接着执行new对象代码,现在就是两个对象了,违反单例模式定义。

优点:延迟加载(lazy load),真正用的时候才加载。

缺点:必须加锁 synchronized 才能保证单例,但加锁会影响效率。

2、饿汉式(单例对象立即加载)

不是延迟加载。

代码实现:

//饿汉式 单例模式
public class SingletonDemo1 {
    
    

    //类初始化时,立即加载这个对象
    private static SingletonDemo1 instance = new SingletonDemo1();

    //构造器私有化。这样该类就不会被实例化
    private SingletonDemo1(){
    
    
    }

    //获取唯一可用的对象
    public static SingletonDemo1 getInstance(){
    
    
        return instance;
    }
}

解释及优缺点:

饿汉式单例模式在类SingletonDemo1加载时就初始化,并创建单例对象,绝对线程安全。

构造器私有化后,就不能通过new获取对象了。

优点:没有加锁,执行效率会提高。

缺点:由于类加载时就初始化,如果没有调用该类,会造成资源浪费。

3、双重校验锁( DCL,即double-checked locking)

JDK1.5 起

是延迟加载。

代码实现:

// 双检锁/双重校验锁 DCL,即 double-checked locking
public class SingletonDemo3 {
    
    
    private volatile static SingletonDemo3 instance;
    private SingletonDemo3(){
    
    };

    public static SingletonDemo3 getInstance(){
    
    
        if(instance == null){
    
    
            synchronized (SingletonDemo3.class){
    
    
                if (instance == null){
    
    
                    instance = new SingletonDemo3();
                }
            }
        }
        return instance;
    }
}

解释及优缺点:

采用双锁机制,安全且在多线程情况下能保持高性能。

进行了两次的判断,第一次是为了避免不要的实例,第二次是为了进行同步,避免多线程问题。

由于instance = new SingletonDemo3();对象的创建在JVM中可能会进行重排序,在多线程访问下存在风险,使用volatile修饰instance实例变量有效,解决该问题。

实现比较困难,一般不推荐使用

4、静态内部类式

是延迟加载。

代码实现:

public class SingletonDemo4 {
    
    

    //静态类内部类
    private static class SingletonHolder{
    
    
        private static final SingletonDemo4 instance = new SingletonDemo4();
    }

    private SingletonDemo4(){
    
    }

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

解释及优缺点:

SingletonDemo4类被装载了,instance不会被实例化SingletonHoldyanshijiazer类没有被主动使用,只有通过getInstance方法才会显式装载 SingletonHolder 类,从而实例化 instance。不像饿汉式那样立即加载。

静态内部类方式能达到双检锁方式一样的功效,但实现更简单。

如果想要实例化instance,但又想让它延迟加载,不想在类初始化的时候就被实例化,这种比较合适。

对静态域使用延迟初始化,应使用这种方式而不是双检锁方式。

这种方式只适用于静态域的情况,双检锁方式可在实例域需要延迟初始化时使用。

5、枚举

JDK1.5 起

不是延迟加载。

代码实现:

public enum SingletonDemo5 {
    
    
    //定义一个枚举的元素,他就代表了Singleton的一个实例。
    INSTANCE;
    //单例可以有自己的操作
    public void singletonOperation(){
    
    
        //功能处理
    }
}

//测试类
class Test5{
    
    
    public static void main(String[] args) {
    
    
        SingletonDemo5 sd = SingletonDemo5.INSTANCE;
        SingletonDemo5 sd2 = SingletonDemo5.INSTANCE;
        System.out.println(sd==sd2);
    }
}

解释及优缺点:

实现简单

这种实现方式还没有被广泛采用,但这是实现单例模式的最佳方法。

它更简洁,自动支持序列化机制,绝对防止多次实例化,避免反射和反序列化问题。

最后:

建议使用第 2种饿汉方式。如果有明确要求延迟加载的时候,建议使用第 5种静态内部类方式。若涉及到反序列化创建对象时,大家也可以尝试使用枚举方式。

猜你喜欢

转载自blog.csdn.net/qq_42524288/article/details/108995716
今日推荐