单例设计模式是23种设计模式中,最基础也是最经常使用的设计模式之一,也是面试中关于设计模式知识点考察比较高频的问题之一。提及单例模式的写法,大多数状况下出如今咱们脑海中的可能就是“饿汉式”,“懒汉式”这两种写法,可是今天小码哥今天要介绍的是单例模式的7种写法,之后面试官要是再问你单例模式,那就抛给他这七种写法吧!面试
接下来,咱们就言归正传,来一一介绍这七种单例模式的写法吧!设计模式
一、饿汉式安全
饿汉式是单例模式设计中比较经典的实现方式。实现代码以下:多线程
//final不容许被继承 public final class SingleTonEhangshi { //实例变量 private byte[] data = new byte[1024]; //在定义实例对象时直接初始化 private static SingleTonEhangshi instance = new SingleTonEhangshi(); //私有化构造函数,不容许外部NEW private SingleTonEhangshi() { } public static SingleTonEhangshi getInstance() { return instance; } }
饿汉式的实现关键在于instance做为类变量直接获得了初始化,若是咱们主动使用SingleToEhangshi类,那么instance实例将会直接完成建立,包括其中的实例变量也都会获得初始化。框架
instance做为类变量,在类初始化的过程当中会被收集进<clinit>()方法中,而该方法是能够100%地保证同步,也就是说instance在多线程的状况下不可能被初始化两次。可是因为instance被ClassLoader加载后很长一段时间才被使用的话,那就会意味着instance实例所开辟的堆内存会驻留很长的时间。socket
整体说来,若是一个类中的成员变量比较少,且占用的内存资源也很少,用饿汉式的方式实现单例模式也何尝不可,只是其没法进行懒加载。ide
二、懒汉式函数
所谓懒汉式就是在使用类实例的时候再去建立,也就是说用到的时候我再建立,这样就能够避免类在初始化的时候提早建立过早地占用内存空间。实现代码以下:性能
//final不容许被继承 public final class SingleTonLhangshi { //实例变量 private byte[] data = new byte[1024]; //定义实例,可是不直接初始化 private static SingleTonLhangshi instance = null; //私有化构造函数,不容许外部NEW private SingleTonLhangshi() { } public static SingleTonLhangshi getInstance() { if (null == instance) { instance = new SingleTonLhangshi(); } return instance; } }
类变量instance=null,所以当类被初始化的时候instance并不会马上被实例化,而是在getInstance()方法被调用时判断instance实例是否被实例化,若是没有实例化在调用私有构造方法进行实例化操做。this
懒汉式写法在多线程环境下,会存在同一时间多个线程同时看到null==instance的状况,从而致使instance会被实例化屡次,从而没法保证单例的惟一性。
三、懒汉式+同步方法
懒汉式的单例实现方式能够保证明例的懒加载,可是却没法保证明例的惟一性。在多线程环境下因为instance为共享数据,当多个线程访问使用时,须要保证数据的同步性,因此若是须要保证懒汉式实例的惟一性,咱们能够经过同步的方式来实现。代码以下:
/final不容许被继承 public final class SingleTonLhangshiSync { //实例变量 private byte[] data = new byte[1024]; //定义实例,可是不直接初始化 private static SingleTonLhangshiSync instance = null; //私有化构造函数,不容许外部NEW private SingleTonLhangshiSync() { } //向getInstance方法加入同步控制,每次只能有一个线程可以进入 public static synchronized SingleTonLhangshiSync getInstance() { if (null == instance) { instance = new SingleTonLhangshiSync(); } return instance; } }
采用懒汉式+数据同步的方法既知足了懒加载又可以100%保证instance实例的惟一性。可是,synchronized关键字的排它性会致使getInstance()方法同一时刻只能被一个线程访问,性能会比较低下。
四、Double-Check
Double-Check是一种比较聪明的设计方式,它提供了一种高效的数据同步策略,那就是首次初始化的时候加锁,以后则容许多个线程同时进行getInstance()方法的调用来得到类的实例。代码以下:
//final不容许被继承 public final class SingletonDoubleCheck { //实例变量 private byte[] data = new byte[1024]; //定义实例,可是不直接初始化 private static SingletonDoubleCheck instance = null; Connection con; Socket socket; //私有化构造函数,不容许外部NEW private SingletonDoubleCheck() { this.con = con;//初始化 this.socket = socket;//初始化 } public static SingletonDoubleCheck getInstance() { //当instance为null时,进入同步代码块,同时该判断避免了每次都须要进入同步代码块,能够提升效率 if (null == instance) { //只有一个线程可以得到SingletonDoubleCheck.class关联的monitor synchronized (SingletonDoubleCheck.class) { //判断若是instance为null则建立 if (null == instance) { instance = new SingletonDoubleCheck(); } } } return instance; } }
当两个线程发现null==instance成立时,只有一个线程有资格进入同步代码块,完成对instance的初始化,随后的线程发现null==instance不成立则无须进行任何操做,之后对getInstance的访问就不会再须要进行数据同步了。
此种方式看起来是既知足了懒加载,又保证了instance实例的惟一性,而且还提供了比较高效的数据同步策略,能够容许多个线程同时对getInstance进行访问。可是这种方式在多线程的状况下,可能会引发空指针异常,这是由于若是在如上代码的构造方法中还存在初始化其余资源的状况的话,因为JVM运行时存在指令重排的状况,这些资源在实例化时顺序并没有先后关系的约束,那么在这种状况下,就极有多是instance最早被实例化,而con和socket并未完成实例化,而未完成实例化的实例在调用其方法时将会抛出空指针异常。
五、Volatile+Double-Check
为了解决Double-Check指令重排致使的空指针问题,能够用volatile关键字防止这种重排序的发生。所以代码只须要稍做修改就能知足多线程下的单例、懒加载以及实例的高效性了。代码以下:
//final不容许被继承 public final class SingletonDoubleCheck { //实例变量 private byte[] data = new byte[1024]; //定义实例,可是不直接初始化 private static volatile SingletonDoubleCheck instance = null; Connection con; Socket socket; //私有化构造函数,不容许外部NEW private SingletonDoubleCheck() { this.con = con;//初始化 this.socket = socket;//初始化 } public static SingletonDoubleCheck getInstance() { //当instance为null时,进入同步代码块,同时该判断避免了每次都须要进入同步代码块,能够提升效率 if (null == instance) { //只有一个线程可以得到SingletonDoubleCheck.class关联的monitor synchronized (SingletonDoubleCheck.class) { //判断若是instance为null则建立 if (null == instance) { instance = new SingletonDoubleCheck(); } } } return instance; } }
六、Holder方式
Holder方式彻底借助了类加载的特色。代码以下:
//不容许被继承 public final class SingletonHolder { //实例变量 private byte[] data = new byte[1024]; private SingletonHolder() { } //在静态内部类中持有单例类的实例,而且可直接被初始化 private static class Holder { private static SingletonHolder instance = new SingletonHolder(); } //调用getInstance方法,事实上是得到Holder的instance静态属性 public static SingletonHolder getInstance() { return Holder.instance; } }
在单例类中并无instance的静态成员,而是将其放到了静态内部类Holder之中,所以单例类在初始化的过程当中并不会建立SingletonHolder的实例,内部类Holder中定义了SingletonHolder的静态变量,而且直接进行了实例化,只有当Holder被主动引用的时候才会建立SingletonHolder的实例。
SingletonHolder实例的建立过程在Java程序编译时期收集至<clinit>()方法中,该方法又是同步方法,能够保证内存的可见性、JVM指令的顺序性和原子性。Holder方式的单例模式设计是最好的设计之一,也是目前使用比较广的设计。
七、枚举方式
枚举方式在不少开源框架中也应用得比较普遍,枚举类型不容许被继承,一样是线程安全的,而且只能被实例化一次,可是枚举类型不可以实现懒加载。用枚举类型,实现单例模式的代码以下:
public class SingletonEnum { //实例变量 private byte[] data = new byte[1024]; private SingletonEnum() { } //使用枚举充当Holder private enum EnumHolder { INSTANCE; private SingletonEnum instance; EnumHolder() { this.instance = new SingletonEnum(); } private SingletonEnum getInstance() { return instance; } } public static SingletonEnum getInstance() { return EnumHolder.INSTANCE.getInstance(); } }
以上就是要给你们介绍的单例模式的7种写法了,虽然单例模式很是简单,可是在多线程的状况下,咱们以前所设计的单例程序未必可以知足单实例、懒加载以及高性能的特色。