设计模式篇|单例模式

一、简介

1.什么是设计模式

设计模式是一套被 反复使用、多数人知晓、经过分类编目的、代码设计经验的总结。

2.为什么要使用设计模式

为了可重用代码,让代码更容易的被他人理解并保证代码的可靠性。

二、GOF23

创建型模式:

单例模式、工厂模式、抽象工厂模式、建造者模式、原型模式

结构性模式:

适配器模式、桥接模式、装饰模式、组合模式、外观模式、享元模式、代理模式

行为型模式

模板方法模式、命令模式、迭代器模式、观察者模式、中介者模式、备忘录模式、解释器模式、状态模式、策略模式、责任链模式、访问者模式

三、单例模式概念及其实现

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

常见应用场景
1.Windows的Task Manager(任务管理器)就是很经典的单例模式
2.Windows的Recycle Bin(回收站)也是典型的单例模式应用,在整个系统运行中,回收站一直维护者仅有的一个实例
3.网站的计数器,一般也采用单例模式,否则难以同步
4.数据库的连接池设计一般也采用单例模式,因为数据库连接是一种数据库资源
5.在Spring中,每个Bean默认就是单例的,这样做的优点是Spring容器可以管理

单例模式的优点

1.由于单例模式只生成一个实例,减少了系统性能开销。
2.单例模式可以在系统设置全局访问点,优化环共享资源访问。

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

  • 静态内部类式(线程安全,调用效率高,可以延时加载)

  • 枚举单例(线程安全,调用效率高,不能延时加载)

  • 饿汉式实现
/**
 * 饿汉式单例模式
 */
public class SingletonDemo01 {

    //1.将实例设置为静态、私有的
    //类初始化时立即加载
    private static SingletonDemo01 instance = new SingletonDemo01();

    //2.将构造器私有
    private SingletonDemo01(){
    }

    //3.提供访问点
    public static SingletonDemo01 getInstance(){
        return instance;
    }
}

在公有方法处不需要添加synchronized,因为静态对象在类加载时进行初始化,此时天然线程安全,但是没有延时加载,如果最终资源没有被使用,会造成资源的浪费。因为没有加同步,所以执行效率高。

  • 懒汉式实现
/**
 * 懒汉式单例模式
 */
public class SingletonDemo02 {

    private static SingletonDemo02 instance;

    private SingletonDemo02(){

    }

    public static synchronized SingletonDemo02 getInstance(){
        if (instance == null){
            instance = new SingletonDemo02();
        }
        return instance;
    }
}

懒汉式在公有方法处需要加synchronized关键字,因为不添加同步的话,在多线程情况下有可能会对单例模式造成破坏。懒汉式加载可以避免资源的浪费,但是因为加了同步,所以调用效率比较低。

  • 双重检测锁实现
/**
 * 双重检测锁式
 */
public class SingletonDemo03 {

    private static SingletonDemo03 instance = null;

    private SingletonDemo03(){}

    public static SingletonDemo03 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;
                }
            }
        }
        return instance;
    }
}

优点:

  • 调用效率高、线程安全、实现了延迟加载

缺点:

  • 由于JVM模型的原因,有时候会出现错误,所以不建议使用
  • 静态内部类式
public class SingletonDemo04 {

    private SingletonDemo04(){}

    private static class SingletonClassInstance{
        private static final SingletonDemo04 instance = new SingletonDemo04();
    }

    public static SingletonDemo04 getInstance(){
        return SingletonClassInstance.instance;
    }
}

要求:

  • 外部没有static属性,则不会像饿汉式那样立即加载对象。

  • 只有真正调用getInstance()方法,才会加载静态内部类,加载类时是线程安全的。instancestatic final类型,保证内存中只有这样一个实例存在,而且只能被赋值一次,从而保证了线程的安全。

  • 兼备了线程安全、调用效率高、延迟加载的优势。

  • 枚举方式
public enum  SingletonDemo05 {

    /**
     * 定义一个枚举元素,他就代表了Singleton的一个实例
     */
    INSTANCE;
}

优点:

  • 实现简单
  • 枚举本身就是单例模式。由JVM从根本上提供保障!避免通过反射和反序列化的漏洞。

缺点:

  • 无延迟加载
如何选用
  • 单例对象、占用资源少、不需要延迟加载时
    使用枚举式好于饿汉式
  • 单例对象、占用资源大、需要延迟加载时
    静态内部类式好于懒汉式
关于单例模式的破解

注意:枚举方式由于JVM底层的原因,所以无法破解
方式一:通过反射

        //通过反射方式直接调用私有构造器
        Class clazz = (Class) Class.forName("com.hxx.singleton.SingletonDemo01");
        //获取构造器
        Constructor constructor = clazz.getDeclaredConstructor(null);
        constructor.setAccessible(true);
        //通过构造器创建实例
        SingletonDemo01 s3 = constructor.newInstance();
        SingletonDemo01 s4 = constructor.newInstance();

        System.out.println(s3 == s4);

方式二:通过反序列化

        //通过反序列化方式调用
        FileOutputStream fos = new FileOutputStream("D:/a.txt");
        ObjectOutputStream oos = new ObjectOutputStream(fos);
        oos.writeObject(s1);

        oos.close();
        fos.close();

        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("D:/a.txt"));
        SingletonDemo01 s3 = (SingletonDemo01) ois.readObject();
        System.out.println(s3);
解决破解

破解反射

    //2.将构造器私有
    private SingletonDemo01(){
        if (instance != null){
            throw new RuntimeException();
        }
    }

在构造器中对instance进行判断

破解反序列化

`  private Object readResolve(){
        return instance;
    }

在单例模式的方法中添加readResolve方法。

你可能感兴趣的:(设计模式篇|单例模式)