单例模式——最全写法汇总

来源:互联网 发布:淘宝直播电脑端怎么看 编辑:程序博客网 时间:2024/06/06 04:21

一、意图与动机

保证一个类仅有一个实例,并且提供一个全局访问点。


二、效果

1、对唯一实例的受控访问。

2、缩小命名空间。单例模式是对全局变量的一种改进,避免污染全局变量空间

3、允许可变数目的实例。可以改进单例模式,精确控制实例个数


三、实现

1、线程不安全的实现(lazy init)

package com.yangyi.singleton;/** * Created by yangjinfeng02 on 2016/9/27. */public class Singleton1 {    private static Singleton1 INSTANCE;    private Singleton1(){}    public static Singleton1 Instance() {        if (INSTANCE == null) {            INSTANCE = new Singleton1();        }        return INSTANCE;    }}

缺陷:多线程下不可用,无法确保只创建一个实例


2、同步方法(lazy init)

针对1中多线程不可用的问题,最直观手段是将访问方法做同步处理,从而实现线程安全的延迟加载

package com.yangyi.singleton;/** * 同步处理,线程安全,延迟加载 * Created by yangjinfeng02 on 2016/9/27. */public class Singleton2 {    private static Singleton2 INSTANCE;    private Singleton2(){}    public static synchronized Singleton2 Instance() {        if (INSTANCE == null) {            INSTANCE = new Singleton2();        }        return INSTANCE;    }}

由于对Instance()方法做了同步处理,能够保证多线程可用,但synchronized将导致性能开销,如果多个线程频繁调用该方法,将会导致程序执行性能的下降。反之,如果该方法调用不频繁,这种实现则是可以使用的。


3、双重检查锁定(lazy init)——不正确的一种实现

为了解决2中同步带来的性能开销,一种思路是双重锁检查,只在第一次创建的时候做同步操作

package com.yangyi.singleton;/** * 双重锁检查,线程不安全,延迟加载 * Created by yangjinfeng02 on 2016/9/27. */public class Singleton3 {    private static Singleton3 INSTANCE;    private Singleton3(){}    public static Singleton3 Instance() {        // 第一次检查        if (INSTANCE == null) {            // 枷锁            synchronized (Singleton3.class) {                // 第二次检查                if (INSTANCE == null) {                    // 创建                    INSTANCE = new Singleton3();                }            }        }        return INSTANCE;    }}

这种实现看起来比较完美,但这是一个错误的优化!

错误的地方:上述代码中第一次判断INSTANCE不为null,读取到的对象可能尚未初始化完毕。

问题的根源

代码中new创建实例的那一行,在jvm中实际做的事情大致可以归结为三点:

(1)、分配对象内存

(2)、初始化对象

(3)、设置INSTANCE指向分配的内存地址

而这3点中,2和3是可能发生重排序的,如果2和3的顺序被重排后,那么执行过程如下

(1)、分配对象内存

(3)、设置INSTANCE指向分配的内存地址

(2)、初始化对象

之所以会发生这种重排序,是因为java语言规范指定,所有线程在执行程序的时候必须遵循intra-thread semantics. intra-thread semantics保证重排序不会改变单线程内的执行结果,换句话说,intra-thread semantics允许那些单线程内不会改变程序执行结果的重排序。


接着来看这种重排序会导致的后果,如果线程A完成了(1)和(3),此时线程B去判断INSTANCE是否为null,此时INSTANCE不为null,但是所指向的内存地址空间尚未初始化完毕,所以此时线程B取到的实例是并未初始化完成的。

解决方案

知晓了问题的根源,我们来看如何解决,有两个思路:

方案一、不允许(2)和(3)重排序

方案二、允许(2)和(3)重排序,但是不允许其它线程看到这个重排序

根据这两种思路,于是有下面的两种实现方案


4、基于volatile的方案

我们知道java中的volatile关键字的作用就是限制指令的重排序,所以相对于3,我们只需要做一个小小的改动就可以将3中的方案变为正确无误、线程安全的实现方式。我们只需要将INSTANCE声明为volatile即可

package com.yangyi.singleton;/** * 基于volatile的双重锁检查,线程安全,延迟加载 * Created by yangjinfeng02 on 2016/9/27. */public class Singleton4 {    private volatile static Singleton4 INSTANCE;    private Singleton4(){}    public static Singleton4 Instance() {        // 第一次检查        if (INSTANCE == null) {            // 枷锁            synchronized (Singleton4.class) {                // 第二次检查                if (INSTANCE == null) {                    // 创建                    INSTANCE = new Singleton4();                }            }        }        return INSTANCE;    }}


5、基于JVM类初始化的解决方案

jvm在类的初始化阶段(即在class被加载后,且被线程使用之前),会执行类的初始化,在初始化期间,JVM会获取一个锁,这个锁可以同步多个线程对同一个类的初始化。

利用这个特性,我们可以实现另外一种线程安全的单例模式

package com.yangyi.singleton;/** * 基于jvm类加载机制的单例实现 * Created by yangjinfeng02 on 2016/9/27. */public class InstanceFactory {    private static class InstanceHolder {        public static Instance INSTANCE = new Instance();    }    public static Instance Instance() {        // 此处将触发InstanceHolder类的初始化        return InstanceHolder.INSTANCE;    }}


另外一种更加简洁的写法,利用静态常量

package com.yangyi.singleton;/** * Created by yangjinfeng02 on 2016/9/28. */public class Singleton5 {    private static final Singleton5 INSTANCE = new Singleton5();    private Singleton5() {};    public static Singleton5 getInstance() {        return INSTANCE;    }}

另外一种实现方式,通过静态代码块

package com.yangyi.singleton;/** * Created by yangjinfeng02 on 2016/9/28. */public class Singleton6 {    private static Singleton6 INSTANCE;    private Singleton6() {};    static {        INSTANCE = new Singleton6();    }    public static Singleton6 getInstance() {        return INSTANCE;    }}

上述三种实现均是基于jvm类初始化的解决方案,没有禁止指令重排,但是通过jvm类初始化时候的锁,将指令重排对第二个线程不可见


6、基于enum的实现方式

在effective java中介绍了一种基于enum实现单例的简洁方法

package com.yangyi.singleton;/** * Created by yangjinfeng02 on 2016/9/28. */public enum Singleton7 {    INSTANCE;    public void leaveTheBuilding() {            }}

四、总结

如此多的实现方案,思路不一样,但是目的是一致的。实际使用中可更具场景选择合适的方案,整体来说,基于类初始化的方案比较受推荐。


0 0
原创粉丝点击