龙空技术网

线程安全的单例模式

Running的程序员 88

前言:

眼前同学们对“单例模式线程安全问题”大致比较着重,你们都需要剖析一些“单例模式线程安全问题”的相关资讯。那么小编也在网上搜集了一些对于“单例模式线程安全问题””的相关知识,希望大家能喜欢,朋友们一起来学习一下吧!

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

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

原理: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() {        //类提供的实例方法,调用方获取单例对象后,可调用类中的实例方法    }}

标签: #单例模式线程安全问题