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

一、意图与动机

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


二、效果

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
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值