设计模式--单例模式
单例模式: 在它的核心结构中只包含一个被称为单例的特殊类。通过单例模式可以保证系统中,应用该模式的类一个类只有一个实例。即一个类只有一个对象实例。
一、饿汉式
饿汉式的关键在于instance作为类变量直接得到初始化,该方法能够百分之百的保证同步,也就是说instance在多线程下也不可能被实例化两次,但是instance被ClassLoader加载后可能很长时间才会被使用,那就意味着instance实例所开辟的空间的堆内存会驻留更久的时间。
如果一个类中的成员属性比较少,所占用的内存资源不多,饿汉式也未尝不可。总结起来,饿汉式可以保证多线程下唯一的实例,getInstance性能也比较高,但是无法进行懒加载。
二、懒汉式(不可用于多线程,不可取)
Singleton2 的类变量instance = null,当Singleton2.class被初始化的时候instance并不会被实例化,在getInstance方法中会判断instance 实例是否被实例化,看起来没什么问题,但在多线程环境下,会导致instance可能被实例化多次。 线程1判断null == instance为true时,还没有实例化instance,切换到了线程2运行,线程2判断null == instance也为true。就会实例化多次。
三、懒汉式 + 同步方法(性能低,不可取)
采用懒汉式 + 数据同步方式既满足了懒加载又能百分之百保证instance实例的唯一性,但是synchronized 关键字天生的排他性导致了getInstance方法只能在同一时刻被一个线程所访问,性能低下。
四、Double-Check(性能不错,但是有可能发生重进入)
当两个线程发现null == instance成立时,只有一个线程有资格进入同步代码块,完成对instance的实例化,随后的线程发现 null == instance 不成立则无须进行任何操作,以后对getInstance的访问就不需要数据同步的保护了。
这种方式看起来那么的完美,既满足了懒加载,有保证instance实例的唯一性。Double-Check的方式提供了高效的数据同步策略,可以允许多个线程同时对getInstance进行访问。但是这种方式有可能引起空指针异常,我们分析一下。
Singleton4的构造函数中,初始化了conn还有Singleton4自身,根据JVM指令重排序和Happens-Before规则,这两者之间的实例化顺序并无前后关系的约束,那么极有可能instance最先被实例化,而conn并未完成实例化,未完成初始化的实例调用其他方法将会抛出空指针异常。
五、Volatile + Double + Check(懒加载,优,可取)
在instance前 加上 volatile的关键字,则可以防止重排序的发生。
六、Holder 方式(跟5的原理一样,但是代码简洁,可取)
在Singleton6中并没有instance的静态变量,而是将其放在静态内部类Holder类中,因此Singleton6初始化过程中并不会创建Singleton6的实例,Holder类中定义了Singleton6的静态变量,并且直接进行了实例化,当Holder被直接饮用的时候则会创建Singleton6的实例,该方法又是同步方法,保证了内存的可见性,JVM的顺序性和原子性。Holder方式是单例设计最好的设计之一。
七、枚举方式(最优,强烈推荐)
枚举类型不允许被继承,同样是线程安全且只能被实例化一次,但是枚举不能够懒加载,对于Singleton6主动使用,比如调用其中静态方法则INSTANCE会立即得到实例化。
也可以对其进行改造,增加懒加载的特性。类似于Holder。
##################### ps: