Singleton Pattern -- 不一样的单例模式

    Singleton Pattern -- 单例模式

  单例模式是用来创建一个只能又一个实例的对象。

  单例模式类图如下。

 

  单例模式有两大好处:

    (1)对于频繁使用的对象,可以省略创建对象所话费的时间,这对于那些重量级对象而言,是非常可观的一笔系统开销。

    (2)由于new操作的次数减少,因而对系统内存的使用频率页会降低,这将减轻GC压力,缩短GC停顿时间。

   因此对于系统的关键组件和被频繁使用的对象,使用单例模式便可以有效地改善系统的性能。

   这种单例的实现方式非常简单,而且十分可靠,它唯一的不足仅是无法对instance实例做延迟加载。假如单例的创建过程很慢,而由于instance成员变量

是static定义的,因此在JVM加载单例类时,单例对象就会被建立,如果此时,这个单例类在系统中还扮演其他角色,那么在任何使用这个单例类的地方都会

初始化这个单例变量,而不管是否被用到。比如单例类作为String工厂,用于创建一些字符串(该类即用于创建单例Singleton,又用于创建String对象):

  单例模式通用代码(饿汉模式):

 

 

   当使用Singleton.createString()执行任务时,程序输出:

 

 

   可以看到,虽然此时并没有使用单例类,但它还是被创建出来,这也许是开发人员所不愿意看到的。为了解决这个问题,并以此提高系统在相关函数调用是的

反应速度,就需要引入延时加载机制。

  延时单例模式(懒汉模式)

 

 

   首先,对于静态成员变量instance初始化赋值null,确保系统启动时没有额外的负载;其次,在getInstance()工厂方法中,判断当前单例是否已经存在,

若存在则返回,不存在则再建立单例。这里尤其还要注意,getInstance()方法必须是同步的,否则再多线程的环境下,当一个线程A执行到instance = new

Singleton(),但还没有获得对象(对象初始化时需要时间的),在此同时第二个线程B也在执行,执行到(instance == null)判断,那么线程B判断的结果

也为真。于是继续运行下去,线程A获得了一个对象,线程B也获得了一个对象,在内存中就出现了两个对象!

   如下:

 

 

   开启了5个线程同时完成以上代码的运行,使用第1种类型的单例耗时0ms,而使用LazySingleton,解决了线程安全问题却相对耗时约390ms。性能至

少相差2个数量级。

  为了使用延时加载引入的同步关键字反而降低了系统性能,有点得不偿失,我们继续改进:

  

 

 

   在这个实现中,单例模式是用来内部类来维护单例的实例,当StaticSingleton被加载时,其内部类并不会被初始化,故可以确保

StaticSingleton类被载入JVM时,不会初始化单例类,而当getInstance()方法被调用时,才会加载SingletonHolder,从而初始化instance。

同时,由于实例的建立是在类加载时完成,故天生对多线程友好,getInstance()方法也不需要使用同步关键字。因此,这种实现方式同时

兼备以上两种实现的优点。

  通常情况下,用以上方式实现的单例已经可以确保在系统中只存在唯一实例了。

  (现在已经标准的实现了单例模式方式有(单检查锁,双检查锁,枚举),但是如果我们通过反射机制调用,是否会产生多个实例,即破坏了单例模式。)

  反射模式,调用方法是不会破坏单例模式,因为反射方法同样受锁&逻辑的保护。

   调用构造函数会破坏单例模式,因为构造方法只是private 修饰,防止外部类访问,但是反射方法访问,不受限制。

  还有一种破坏次单例模式的方法:对象克隆。这个现象是我在看原型模式时发现。

  如果类实现了Clonable接口,那么在得到单例模式对象可以通过clone方法生成单例对象。

  所以写单例模式时不要继承或间接继承Clonable接口。

 

 

 

 

 

 

    

posted @ 2019-09-09 18:13  加了冰的才叫可乐  阅读(219)  评论(0编辑  收藏  举报