单例模式

定义

单例模式(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;
     }
 ​
 ​
 }

 

  

单例模式总结

  • 优点:

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

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

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

  • 缺点

    没有接口,扩展困难

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

posted @ 2020-11-16 18:21  xdsax  阅读(90)  评论(0编辑  收藏  举报