GOF23设计模式之 单例模式

单例设计模式

核心作用

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

常见应用场景:

  • windows的任务管理器
  • 网站的计数器
  • Spring中,每个Bean默认就是单例的
  • 等等

单例模式的优点:

  • 由于单例设计模式只生成一个实例,减少了系统性能开销,当一个对象的产生需要比较多的资源时,如读取配置、产生其他以来对象时,则可以通过在应用启动时直接产生一个单例对象,然后永久驻留内存的方式来解决
  • 单例模式可以在系统设置全局的访问点,优化共享资源访问,例如可以设计一个单例类,负责所有数据表的映射处理。

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

  • 主要:
    • 懒汉式(线程安全,调用效率高。但是,不能延时加载。)
    • 饿汉式(线程安全,调用效率不高。但是,可以延时加载。)
  • 其他:
    • 双重检测锁式(由于JVM底层内部模型原因,偶尔会出现问题。不建议使用)
    • 静态内部类式(线程安全,调用效率高,但是,可以延时加载)
    • 枚举单例(线程安全,调用效率高,不能延时加载)

饿汉式实现(单例对象立即加载)

public class SingletonDemo02{
        private static /*final*/ SingletonDemo02 s = new SingletonDemo02();
        private SingletonDemo02(){} //私有化构造器
        public static /*synchronized*/ DingletonDemo02 getInstance(){
            return s;
        }
    }


public class Client{
    public static void main(String[] args){
        SingletonDemo02 s = SingletonDemo02.getInstance();
        SingletonDemo02 s2 = SingletonDemo02.getInstance();
        System.out.println(s==s2); //结果为true
    }
}
  • 饿汉式单例模式代码中,static变量会在类装载时初始化,此时也不会涉及多个线程对象访问该对象的问题。虚拟机保证只会装载一次该类,肯定不会发生并发访问问题。因此,可以省略synchronized 关键字
  • 问题:如果只是加载本类,而不是要调用getInstance(), 甚至永远没有调用,则会造成资源浪费!

懒汉式实现(单例对象延迟加载)

public class SingletonDemo02{
    private static SingletonDemo02 s;

    private SingletonDemo01(){} //私有化构造器

    public static synchronized SingletonDemo02 getInstance(){
        if(s==null){
            s = new SingletonDemo02();
        }
        return s;
    }
}
  • 要点:
    • lazy load! 延迟加载,懒加载! 真正用的时候才去加载!
  • 问题:
    • 资源利用率高了。但是,每次调用getInstance()方法都要同步,并发效率低。

双重检测锁实现

public class SingletonDemo03{
    private static SingeltonDemo03 instance = null;
    public static SingeltonDemo03 getInstance(){
        if(instance == null){
            SingletonDemo03 sc;
            synchronized(SingletonDemo03.class){
                sc = instance;
                if(sc == null){
                    synchronized(SingletonDemo03.class){
                        if(sc == null){
                            sc = new SingletonDemo03();
                        }
                    }
                    instance = sc;
                }
            }
        }
        reutrn instance;
    }
    private SingletonDemo03(){
    }
}
  • 这个模式将同步内容下发到if内部,提高了执行的效率,不必每次获取对象时都进行同步,只有第一次才同步创建了以后就没必要了。
  • 问题:
    • 由于编译器优化原因和JVM底层内部模型的原因,偶尔会出问题,不建议使用

静态内部类实现方式(也是一种懒加载方式)

public class SingletonDemo04{
    private static class SingletonClassInstance{
        private static final SingletonDemo04 instance = new SingletonDemo04();
    }
    public static SingletonDemo04 getInstance(){
        return SingletonClassInstance.instance;
    }
    private SingletonDemo04(){
    }
}
  • 要点:
    • 外部类没有static属性,则不会像饿汉式那样立即加载对象
    • 只有真正调用getInstance(), 才会加载静态内部类。加载类时是线程安全的。instance是static final类型,保证了内存中只有这样一个实例存在,而且只能被赋值一次,从而保证了线程安全性。
    • 兼备了并发高效性和延迟加载优势!

用枚举实现单例模式

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

public static void mian(String[] args){
    SingletonDemo05 sd = SingletonDemo05.INSTANCE;
    SingletonDemo05 sd2 = SingletonDemo05.INSTANCE;
    System.out.println(sd==sd2);
}
  • 优点:
    • 实现简单
    • 枚举本身就是单例模式。由JVM从根本上提供保障!避免通过反射和反序列化的漏洞!
    • 无延迟记载

如何选用?

  • 单例对象,占用资源少,不需要延时加载:
    • 枚举式 好于 饿汉式
  • 单例对象 占用资源大,需要延时加载
    • 静态内部类 好于 懒汉式

防止破解单例模式(不包含枚举实现)

  1. 通过反射破解单例模式
    • 可以在构造方法中手动抛出异常
  2. 通过反序列化破解单例模式
    • 可以通过定义readResolve()防止获得不同对象,(实际是一种回调),定义返回哪个对象。

你可能感兴趣的:(Java)