单例模式是指确保在任何情况下,一个类只有一个实例,并提供一个全局的访问点。这样减少了内存开销,也避免了对资源的多重占用。单例模式看起来简单,实现起来也很简单。缺点是没有接口,扩展困难,若要扩展单例对象,只能修改代码,没有其他途径。

  一、饿汉式单例:加载类时创建类对象,绝对线程安全,在线程还未出现以前就实例化了,不存在线程安全问题。

    优点:没有任何锁,执行效率高,从用户体验上来说,比懒汉式好。

    缺点:类加载时就初始化,不管用与不用,都占着内存空间,浪费了内存,当实例多时,就严重浪费内存。

  二、懒汉式单例:被外部类调用时内部类才加载。有线程安全问题存在。

    解决线程安全方式:

      1:访问点方法加sycronize关键字,但当线程数量比较多时,CPU分配压力上升,会导致大批线程阻塞,从而使程序性能大幅下降。

      2:双重检查锁,在访问点内部加sycronize关键字,可能导致方法内部阻塞,程序性能优于第一种方法。

      3:使用静态内部类,在内部类里面初始化实例,利用了调用方法前先实例化内部类的特性,程序性能更好。

    防止利用反射破坏单例:虽然构造方法私有化了,但还是可以通过反射创建实例,这样就破坏了单例对象的唯一性。

      解决方法:构造方法检查,若实例 !=null,抛出异常,不能创建实例对象。

  三、单例实现方式

    饿汉式

    懒汉式

    序列化式  写readResolve(),可避免序列化破坏单例

    枚举式:是最推荐的写单例的方式。

    容器式

    threadlocal线程单例:threadlocal不能保证其创建的对象是全局唯一,但可以保证单个线程内是唯一的,天生的线程安全。

      经过多线程测试发现,单个线程的实例对象总是唯一的,多个线程的实例是不同的。通过源码发现,threadlocal将所有对象都放到threadlocalmap中,key是

      线程,value是对象,以空间换时间来实现线程间隔离的。

      伪线程安全,在一个线程内部,线程是安全的,只有一个对象。在一个线程外部,线程不安全,可创建多个对象。

      对threadlocal的应用:Spring源码中,使用它来达到多数据源的动态切换。

  三、其他可能出现问题:

    1.有时单例需被序列化写入磁盘,然后再从磁盘反序列化读出实例对象,此时会创建一个新的对象,破坏了单例对象唯一性,怎么办?

      A1:从JDK源码中,可以看到只需增加readResolve方法就可以解决。JDK考虑到这种情况,已经解决,但虽然返回了原先的实例对象,但从源码可以看到它中

    途有重新创建新的实例对象,浪费了内存资源,显然,这种不是最好的解决方式。

      A2:注册式单例:注册式单例也叫登记式单例,是把每一个实例都注册到某一个地方,使用唯一的标识获取实例。注册式单例有两种写法,

        1)枚举:是天生的写单例的方式,不存在线程安全问题,自动解决序列化反序列化破坏单例,用反射破坏单例的各种问题。特点:简单好用。

          注:将代码反编译查看源码就会发现,枚举式单例在静态代码块中就给实例赋值,是饿汉模式的实现。

            序列化不能破坏单例的原因:查看JDK源码发现,枚举类型实际通过类名和class枚举对象找到一个唯一的枚举对象。因此,枚举对象不能被类

              加载器多次加载。

            不能被反射破坏的原因:JDK不让枚举类用反射实例化。

        2)容器缓存:容器缓存用于创建实例非常多的情况,便于管理。但它是非线程安全的,可通过加锁实现安全。