单例模式
1、饿汉式
1.1 代码1.2 理解1.3 总结 2、懒汉式
2.1 代码2.2 理解2.3 总结 3、懒汉式+同步
3.1 代码3.2 理解3.3 总结 4、Double-Check
4.1 代码4.2 理解4.3 总结 5、Double-Check + volatile
5.1 代码5.2 理解5.3 总结 6.Holder方式
6.1 代码6.2 理解6.3 总结 7、枚举方式
7.1 代码7.2 理解7.3 拓展 单例模式
单例模式(Singleton)是一种常见的设计模式。在java应用中,单例对象能够保证在一个JVM中,该对象只有一个实例存在;
好处:
在某些类创建比较频繁,对于一些大型的对象,这是一笔很大的开销;省去了new操作符,降低了系统内存的使用频率,减轻GC压力;保证核心交易的服务器独立控制整个流程。 1、饿汉式 1.1 代码public final class Singleton { private static Singleton singleton = new Singleton(); private Singleton(){} public static Singleton getInstance() { return singleton; }}
1.2 理解饿汉式的关键在于singleton作为类变量直接得到初始化,也就是当我们使用Singleton类的时候,singleton实例直接完成创建,如果该类中存在其他变量,也会直接完成创建。
singleton作为类变量在类初始化的过程中会被手机()方法中,该方法能够百分之百保证同步,也就是singleton能够在多线程的情况下保证实例的唯一性,而不会被创建两次,但是singleton被ClassLoader加载后可能很长一段时间才被使用,那就意味着singleton实例所开辟的堆内存会驻留更久的时间。
如果一个类中的成员属性较少,且占用的内存资源不多,饿汉式方法可以使用,但是,如果一个类成员属性较多并且包含比较多的资源,这种方式则不太适用。
1.3 总结饿汉式的单例设计模式可以保证多个线程下唯一实例,getInstance方法性能比较高,但是无法懒加载。
2、懒汉式 2.1 代码public final class Singleton1 { private static Singleton1 singleton = null; private Singleton1(){} public static Singleton1 getInstance() { if (singleton == null) { singleton = new Singleton1(); } return singleton; }}
2.2 理解懒汉式就是在使用类实例的时候再去创建(用时创建),这样就避免类在初始化时提前创建。
当类初始化的时候,signleton并不会被初始化,在调用getInstance方法的时候,会判断singleton是否为空,如果不为空,则进行实例化。当然这种情况在单线程环境中是不会有什么问题的,但是当getInstance方法在多线程的情况下,则会导致singleton被实例化多次的情况,不能够保证单例的唯一性。
2.3 总结懒汉式的单例设计模式:顾名思义,能够实现懒加载,但是不能够保证多线程环境的实例唯一。
3、懒汉式+同步 3.1 代码public final class Singleton2 { private static Singleton2 singleton = null; private Singleton2(){} public static synchronized Singleton2 getInstance() { if (singleton == null) { singleton = new Singleton2(); } return singleton; }}
3.2 理解懒汉式的方法可以保证实例的懒加载,但无法保证实例的唯一性,在多线程的情况下,singleton又被称为共享资源(数据),在多线程对其访问的时,需要保证数据的同步。
在getInstance()方法中添加synchronized关键字,保证只有一个线程能够进入,保证多线程的情况下,能够保证实例的唯一性,但是synchronized关键字的排他性,导致getInstance()方法在同一时刻只能被一个线程访问,性能低下。
3.3 总结懒汉式+同步synchronized方法,能够保证实例的唯一性,但是synchronized的排他性,导致性能低下。
4、Double-Check 4.1 代码public final class Singleton3 { private static Singleton3 singleton = null; private Singleton3(){} public static Singleton3 getInstance() { if (null == singleton) { synchronized (Singleton3.class) { if (null == singleton) { singleton = new Singleton3(); } } } return singleton; }}
4.2 理解Double-Check提供了一种高效的数据同步策略,那就是首次初始化的时候加锁,之后则允许多线程同时进行方法的调用访问。
这种方法能够满足懒加载,又保证了实例的唯一性,同时掘弃了synchronized加载方法上造成的效率低下,但是这个方法还是存在一定的问题,如上述代码不会存在什么问题,但是当类Singleton中存在其他成员变量的时候,可能由于重排序的问题导致空指针的出现。
空指针:
当Singleton中存在其他成员变量需要初始化,根据JVM运行时指令的重排序和Happens-before原则,若是singleton被先初始化,则其余成员变量未被实现,若此时调用,则会导致空指针异常
Double-Check,高效的数据同步策略,实现了懒加载,保证了实例的唯一性,掘弃了synchronized加载方法上造成的效率低下,但是当类中存在多个成员变量的时候,可能出现空指针的问题。
5、Double-Check + volatile 5.1 代码public final class Singleton4 { private Connection connection; private volatile static Singleton4 singleton = null; private Singleton4(){ //省略具体实现方法,过多,举个栗子多变量的情况 this.connection = new Connection(){}; } public static Singleton4 getInstance() { if (null == singleton) { synchronized (Singleton4.class) { if (null == singleton) { singleton = new Singleton4(); } } } return singleton; }}
5.2 理解在Double-Check中,说明如果多变量情况下可能出现空指针的情况,使用volatile进行保证程序的循序加载
注意点:
成员变量需要在signleton之前 5.3 总结Double-Check+valatile能够保证懒加载、多线程单例且获取高效,且不会发生重排序导致的空指针问题。
6.Holder方式 6.1 代码public final class Singleton5 { private Singleton5(){} private static class Holder { private static Singleton5 singleton5 = new Singleton5(); } public static Singleton5 getInstance() { return Holder.singleton5; }}
6.2 理解Holder方式完全借助了类加载的特点:
当Sigleton5初始化过程中并不会创建Signleton5的实例Holder类中直接实例化singleton5所以当Holder被主动引用时才会创建singleton5的实例Singleton5的实例创建过程在java程序编译时期收集至()方法中,该方法是同步方法,保证内存可见性、JVM指令的顺序性和原子性。Holder方式是单例设计中目前最好的设计之一。
6.3 总结Holder方法,能够保证懒加载,保证实例的唯一性,同事因为是类初始化加载所以能保证内存的可见性以及JVM指令的有序性和原子性。
7、枚举方式 7.1 代码public enum Singleton6 { INSTANCE; Singleton6(){ System.out.println("Singleton6 初始化!"); } public static Singleton6 getInstance() { return INSTANCE; } public static void method(){}}
7.2 理解枚举方式,因为枚举类型本身不允许被继承,同时是线程安全的且只能被实例化一次,但是枚举不能够实现懒加载,调用静态方法立即会被实例化,上诉代码中增加了静态方法,可以实验一下。
7.3 拓展枚举+holder方法实现懒加载,代码如下:
public class Singleton7 { private Singleton7(){} private enum HolderEnum { INSTANCE; private Singleton7 singleton7; HolderEnum() { this.singleton7 = new Singleton7(); } private Singleton7 getSingleton7(){ return singleton7; } } public static Singleton7 getInstance() { return HolderEnum.INSTANCE.getSingleton7(); }}
拓展:在类中增加Holder枚举的方式,实现枚举的懒加载。
学习自:
《Java高并发编程详解——多线程与架构设计》 汪文君