一、单例模式

        单例模式就是保证一个类只能有一个实例对象

1.实现方式

        单例模式有5种常见的实现方式。

(1)饿汉式

        饿汉式是在创建类时就进行实例化(类饿的在创建时就带了实例),以后不再改变。饿汉式在类创建的同时就已经创建好一个静态对象,所以天生是线程安全的。
        实现的要点有三个:
  • 构造私有——避免该类在外部被实例化,也就是说只能自己实例化自己。
  • 私有静态成员变量
  • 公共静态方法——用来获取静态成员变量
        

        1)破坏饿汉式单例的几种情况

  • 反射
            通过反射获取类的构造器,然后将私有构造改为公共构造,就能通过构造器创建一个实例了。

    【预防反射破坏单例】
            在私有构造中加一个if判断,如果实例为空,才允许创建;如果实例不为空就抛出异常。

  • 反序列化
            如果单例类实现了Serializable接口,那么就可以通过反序列破坏单例。先将单例类序列化成字节流,再反序列化成一个实例,这个实例不通过构造方法创建!

    【预防反序列化破坏单例
            在单例类中用readResolve()方法返回我们创建的实例对象,这样再进行反序列化时,就会返回我们创建的实例对象,而不会创建一个新对象。

  • Unsafe
            可以通过反射获取unsafe对象,再调用allocateInstance(Singleton.class)来创建新的实例。
    【暂无预防措施】

(2)枚举Enum(也属于饿汉式)

        java中的枚举类本身也是一种单例模式,而且最重要的是,枚举单例可以防止反射和反序列化破坏单例
  • 创建枚举类Singleton
  • 定义枚举变量INSTANCE
        

(3)懒汉式

        懒汉式单例是在第一次实例化时才去创建实例,而不是像饿汉式那样创建类时就创建了实例。
        
        1)多线程不安全问题
                在多线程下,懒汉式不能保证单例的唯一性,也就是说可能创建出多个实例。比如有两个线程1、2,线程1先调用了getINSTANCE()方法,判断if条件成立,准备创建实例。但这时,线程1还没有创建出实例,线程2也调用了getINSTANCE(),判断if条件成立,这时线程1和线程2都能创建出一个实例,这样就不是单例了。
        2)★解决懒汉式多线程不安全问题的方案
                三种解决方案都是对getINSTANCE()进行改造:
  • synchronized

    【缺点】
            性能差,只有第一次访问才需要加锁。
  • ★双重检查锁定(双检锁DCL)
            双检锁是在加synchronized前先判断是否已经有实例了,第一次访问没有实例才去加锁创建实例,其他的再访问有实例了直接返回实例即可。

    【为什么要两次判断(为什么要检查两次)?】
            如果没有第二个判断条件的话,没创建实例前有两个线程1、2都通过了第一个if判断,线程1拿到了锁,线程2挂起,当线程1创建了实例释放锁之后,线程2会拿到锁,因为没有第二个if判断,所以线程2不管三七二十一还是会创建一个实例,所以要检查两次。
    【为什么要加volatile?】
            volatile可以避免指令重排
            在new Singleton()时,其实要分三步:①为INSTANCE分配内存空间→②(调用构造方法)初始化INSTANCE→③将INSTANCE指向分配好的内存地址。如果不加volatile,有两个线程1、2,都调用getINSTANCE()时,线程1分配完内存空间直接指向内存地址,此时线程2执行第一个if判断,发现INSTANCEY不为空,线程2就返回实例了,但是这个实例其实还没有被初始化,是不完整的
    缺点
            性能也差,使用volatile会降低吞吐量。
  • ★静态内部类懒汉式

2.饿汉式和懒汉式的区别?

  • 资源加载和性能来说:
    • 饿汉式是在创建类的同时就实例化了一个静态对象,所以不管以后会不会用到,都会占据一定的内存;优点就是第一次调用时速度更快(因为早在创建类时就初始化好了,不用现调现初始化)。
    • 懒汉式是调用getINSTANCE()时才会实例化对象,所以第一次调用时需要进行初始化,性能上有些延迟。
  • 线程安全上来说:
    • 饿汉式是在创建类时就实例化了一个对象,所以天生就是线程安全的;
    • 懒汉式是非线程安全的,为了实现线程安全可以通过加synchronized、双检锁、静态内部类的方式,这三种不同的实现方式又有区别:
      • 加synchronized,虽然保证线程安全了,但是每次都要同步执行,影响性能;
      • 双检锁通过在getINSTANCE()中进行两次if判断,确保了只有第一次调用时才需要同步,避免了每次都要同步的性能损耗;
      • 静态内部类的方式利用类加载器(classloader)的机制来保证初始化实例时只有一个线程,既保证了线程安全,又不影响性能。

3.jdk中有哪些地方体现了单例模式?

(1)Runtime类(饿汉式)

        

(2)Console类(双检锁)

        

(3)Collections工具类

        Collections工具类中有一些以Empty开头的类,都是懒汉式的单例模式。
二、