设计模式三十六计之单例模式(Singleton)

解释:单例模式是为了确保在整个应用生命周期内一个类只有一个实例化对象,并且提供该实例化对象的全局访问入口

1.结构和原理

一个最基本的单例模式类包含一个私有的静态变量、一个私有的构造函数和和一个共有的静态函数。其中私有构造函数保证了该类不能通过构造函数来实例化,只能通过共有的静态函数返回一个唯一的私有静态变量。

2.实现单例模式

  • 懒汉模式———线程不安全

    public class Singleton{
      private static Singleton instance;
      private Singleton(){}
    
      public static Singleton getInstance(){
        if(instance == null){
          instance = new Singleton();
        }
        return instance;
      }   
    }
    

    在上述实现中,私有静态变量instance被延迟实例化,这样做的好处是在实际项目中,如果Singleton类没有被使用到,它就不会被实例化,从而减小系统开销。

    注意:懒汉模式在多线程环境中是不安全的,例如当前有n个线程同时执行 getInstance() 方法时,此时的instance都为null,那么Singleton类就会被实例化n次。这与单例模式的设计初衷相悖。

  • 饿汉模式———线程安全

    public class Singleton{
      private static Singleton instance = new Singleton();
      private Singleton(){}
      public static Singleton getInstance(){
        return instance;
      }
    }
    

    备注:在懒汉模式中,线程不安全是由于instance被多次实例化所造成的,在饿汉模式中直接实例化Singleton就解决了线程不安全问题。但是这种方式就失去了延迟实例化的好处。

  • 双重校验锁模式———线程安全

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

    在双重校验锁模式下,双重锁先判断instance是否被实例化,如果instance没有被实例化,则将实例化instance的语句进行加锁操作,只有当instance真正为null时,才去实例化Singleton。instance只会被实例化一次,之后将不会被再次实例化。

    说明:volatile关键字的作用是在多线程环境下,禁止JVM的指令重排列

  • 静态内部类模式

    public class Singleton{
      private Singleton(){}
    
      public static Singleton getInstance(){
        return SingletonProvider.INSTANCE;
      }
    
      public static class SingletonProvider{
        private static final Singleton INSTANCE = new Singleton();
      }
    }
    

    静态内部类模式与饿汉模式有异曲同工之处

  • 枚举模式

    public enum Singleton{
      INSTANCE;
      private String name;
      //getter()...
      // setter()...
      // otherMethod()...
    }
    

    使用方式:

    public class UserEnumSingleton{
      Singleton instance = Singleton.INSTANCE;
      instance.setName("example");
      System.out.println(instance.getName());
      Singleton instance2 = Singleton.INSTANCE;
      instance2.setName("example2");
      System.out.println(instance2.getName());
      instance.otherMethod();
      //other options...
      //使用java反射原理操作
      try{
        Singleton[] enums = Singleton.class.getEnumConstants();
        for(Singleton instance : enums){
          System.out.println(instance.getName());
        }
      }catch(Exception e){
        e.printStackTrace();
      }
    }
    

    控制台输出结果:

    example
    example2
    example2
    

    借助JDK的枚举来实现单例模式,不仅能够避免多线程同步问题,而且还能防止反序列化重新创建新的对象。

3.总结

优点

单例模式使得应用系统中一个类只被实例化一次,节省系统资源开销,对于系统中需要频繁创建和销毁的对象,使用单例模式可以在一定程度上提高系统的性能。

缺点

由于采用单例模式对类进行设计,就必须要记住获取对象的入口,即共有的静态函数名,而不是采用new关键字进行类的实例化,这在多人协同开发的项目中会给开发人员带来一些困扰(看不到源码),因此需要统一编码规范。

适用的范围

  • 需要频繁的进行创建和销毁的类
  • 实例化过程中耗费时间过长、占用资源过多且被频繁调用的类
  • 频繁操作I/O流或者访问数据库的类
  • 应用中定义的工具类