线程安全的单例模式

单例模式,即我们只允许一个类有且仅有一个实例对外提供服务。通常为了性能考虑,单例模式会以懒加载的形式创建,也即单例中的懒汉模式,与之相对的当然就是单例的饿汉模式:不管用不用,我可以先给你创建出来。

1.单线程下的单例模式实现

非多线程环境下,由于不存在线程对共享数据(对象)的竞争,所以也就没有线程安全问题,其单例模式实现如下:

public final class FooSingleton {

    //持有类的唯一实例
    private static FooSingleton instance = null;

    /**
     * 私有构造函数
     */
    private FooSingleton() {
        //do nothing
    }

    /**
     * 提供外部获取实例的工厂方法
     * 非线程安全
     * @return
     */
    private static FooSingleton getInstance(){
        if (null == instance) {//非线程安全
            instance = new FooSingleton();
        }
        return instance;
    }

    public void methodOther() {
        //类提供的实例方法,调用方获取单例对象后,可调用类中的实例方法
    }

}

2.多线程下的单例模式实现

可以发现,在多线程环境下,这个获取单例的方法不是线程安全的:

private static FooSingleton getInstance(){
        if (null == instance) {//非线程安全
            instance = new FooSingleton();
        }
        return instance;
    }

这就会导致客户端(也就是各个调用端)获取到的对象并不是同一个对象,这显然就违背了我们使用单例模式的初衷了。怎样保证线程安全呢?常用的实现方法有如下三种:

2-1 直接加锁实现线程安全的单例模式

直接加锁实现线程安全的单例模式:

public final class FooSingletonSafety {

    //持有类的唯一实例
    private static FooSingletonSafety instance = null;

    /**
     * 私有构造函数
     */
    private FooSingletonSafety() {
        //do nothing
    }

    /**
     * 提供外部获取实例的工厂方法
     * 线程安全:加锁实现
     * synchronized保障了原子性、可见性和有序性
     * @return
     */
    private static FooSingletonSafety getInstance(){
        synchronized (FooSingletonSafety.class) {
            if (null == instance) {
                instance = new FooSingletonSafety();
            }
        }

        return instance;
    }

    public void methodOther() {
        //类提供的实例方法,调用方获取单例对象后,可调用类中的实例方法
    }

}

显然,直接加锁是可以保证单例模式的线程安全的,但是你可能会发现,这种实现方式下,每个线程在获取单例对象的时候都要去抢锁,获取之后再释放锁,这显然效率是不高的(每次获取对象都要有锁开销)。所以,一般我们会通过下面的DCL去实现线程安全的单例模式

2-2 DCL实现线程安全的单例模式

DCL,即double-check-locking双重检查锁定,代码实现如下:

public final class FooSingletonBasedDCL {

    //持有类的唯一实例
    private static volatile FooSingletonBasedDCL instance = null;

    /**
     * 私有构造函数
     */
    private FooSingletonBasedDCL() {
        //do nothing
    }

    /**
     * 提供外部获取实例的工厂方法
     * 线程安全:DCL实现
     * synchronized保障了原子性、可见性和有序性
     * volatile保证校验时(null == instance) instance对各个线程是可见的,同时禁止JIT和处理器重排序
     * @return
     */
    private static FooSingletonBasedDCL getInstance(){
        if (null == instance) {
            synchronized (FooSingletonSafety.class) {
                if (null == instance) {
                    //new关键字对应三条指令:1.分配对象所需的存储空间  2.实例化对象  3.将对象引用赋值给变量
                    //这三条指令正常执行顺序是1->2->3,但是JIT编译器或处理器在执行时可能会将其优化为1->3->2
                    //所以,为了保证instance的可见性以及禁止重排序,所以instance变量必须要用volatile修饰
                    //否则,极端情况下,一个线程在判断if (null == instance)时,instance不为空直接返回,但此时
                    //instance可能是个半对象(对象并没有经过构造方法初始化),这将会造成代码错误
                    instance = new FooSingletonBasedDCL();
                }
            }
        }

        return instance;
    }

    public void methodOther() {
        //类提供的实例方法,调用方获取单例对象后,可调用类中的实例方法
    }

}

DCL你可能开发中基本没用过,但是这种实现方式在很多开源框架如Spring、dubbo等中都有使用

2-3 利用类加载机制实现线程安全的单例模式

利用Java的类加载机制:加载->链接->初始化,我们可以轻易的实现线程安全的单例模式,这种单例模式形式最简单,代码实现如下:

public final class BarSingleton {
    //利用类初始化只会发生一次,创建线程安全的单例
    private static final BarSingleton INSTANCE = new BarSingleton();

	/**
     * 私有构造函数
     */
    private BarSingleton() {
        //do nothing
    }
    
    public static BarSingleton getInstance() {
        return INSTANCE;
    }

    public void methodOther() {
        //类提供的实例方法,调用方获取单例对象后,可调用类中的实例方法
    }
}
  • 6
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值