• 单例模式


    定义

    单例模式(SingletonPattern)是指确保一个类在任何情况下都绝对只有一个实例,并提供一个全局访问点。单例模式是创建型模式。

    饿汉式

    • 优点:没有加任何的锁、执行效率比较高,在用户体验上来说,比懒汉式更好。

      缺点:类加载的时候就初始化,不管用与不用都占着空间,浪费了内存,有可能占着茅坑不拉屎。

     public class Singleton01 {
     ​
         private Singleton01() {
         }
     ​
         private static final Singleton01 instance = new Singleton01();
     ​
         public static Singleton01 getInstance() {
             return instance;
         }
     }

    懒汉式

    dcl(双重检查)

      

     public class Singleton02 {
     ​
         private Singleton02() {
         }
     ​
         // 禁止指令重排序
         private volatile static Singleton02 instance = null;
         // 双重检查
         private static Singleton02 getInstance() {
             if(instance == null){
                 synchronized (Singleton01.class){
                     if(instance == null){
                         instance = new Singleton02();
                     }
                 }
             }
             return instance;
         }
     }

    静态内部类

    • 静态内部类在外部类加载的时候不会被加载,只有外部类中用到内部类时候才会被加载

    • 由于静态内部类没有使用任何锁机制,所以性能优于双重检查实现方式。

     

     public class Singleton03 {
     ​
         private Singleton03() {
         }
     ​
         public static Singleton03 getInstance(){
             return Singleton03Holder.lazy;
         }
         // 静态内部类
         private static class Singleton03Holder {
             public final static Singleton03 lazy = new Singleton03();
         }
     }

     

    枚举单例

    • jvm判断了枚举无法反射获取对象,无法序列化,防止了反射破坏单例和序列化破坏单例

     public enum SingletonEnum {
     ​
         INSTANCE;
     ​
         private Object data = new Object();
     ​
         public Object getData(){
             return data;
         }
     ​
         public static SingletonEnum getInstance(){
             return INSTANCE;
         }
     }

      

    ThreadLocal在线程内实现单例

    • 使用ThreadLocal动态切换数据源

    反射破坏单例以及解决方案

    • 以上三种单例的写法已经很完善了,但是挡不住反射创建对象,调用者反射破坏单例

    public class ReflectBlockSingleton {
     ​
         /**
          * 反射破坏单例
          * @param args
          */
         public static void main(String[] args) {
             Class<Singleton01> clazz = Singleton01.class;
             try {
                 Constructor<Singleton01> constructor = clazz.getDeclaredConstructor(null);
                 constructor.setAccessible(true);//强吻
                 Singleton01 s1 = constructor.newInstance();
                 Singleton01 s2 = Singleton01.getInstance();
                 System.out.println(s1 == s2); // false
             } catch (Exception e) {
                 e.printStackTrace();
             }
         }
     }
    • 解决方案:在私有构造方法中增加判断          
    private Singleton01() {         if(instance != null){
                 throw new RuntimeException("实例已经创建!");
             }
         }

      

    序列化破坏单例以及解决方案

    • 将单例实例创建出来后,序列化到一个文件中,再读出来反序列化为对象

     
    public class SeriableBlockSingletonTest {
         public static void main(String[] args) {
     ​
             Singleton01 s1 = null;
             Singleton01 s2 = Singleton01.getInstance();
     ​
             FileOutputStream fos = null;
             try {
                 fos = new FileOutputStream("SeriableSingleton.obj");
                 ObjectOutputStream oos = new ObjectOutputStream(fos);
                 oos.writeObject(s2);
                 oos.flush();
                 oos.close();
     ​
     ​
                 FileInputStream fis = new FileInputStream("SeriableSingleton.obj");
                 ObjectInputStream ois = new ObjectInputStream(fis);
                 s1 = (Singleton01)ois.readObject();
                 ois.close();
     ​
                 System.out.println(s1);
                 System.out.println(s2);
                 System.out.println(s1 == s2);
     ​
             } catch (Exception e) {
                 e.printStackTrace();
             }
         }
     }
     解决方案
     增加readResolve方法,还是创建了两次对象,只不过在jvm层面被覆盖了。反序列化出来的对象会被GC回收
         
         public class Singleton01 implements Serializable {
     ​
         private Singleton01() {
             if(instance != null){
                 throw new RuntimeException("实例已经创建!");
             }
         }
     ​
         private static final Singleton01 instance = new Singleton01();
     ​
         public static Singleton01 getInstance() {
             return instance;
         }
     ​
         private Object readResolve(){
             return instance;
         }
     ​
     ​
     }

      

    单例模式总结

    • 优点:

      内存只有一个实例,减少了内存开销

      可以避免对资源的多重占用

      设置全局访问点,严格控制访问

    • 缺点

      没有接口,扩展困难

      如果要扩展单例对象,只能修改代码,没有其他途径,不符合开闭原则

  • 相关阅读:
    Maven
    Maven
    Maven
    Maven
    Maven
    Maven
    Maven
    Maven
    Maven教程
    SASS
  • 原文地址:https://www.cnblogs.com/linqing001/p/13986429.html
Copyright © 2020-2023  润新知