线程安全的单例模式

createh53周前 (04-11)技术教程17

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

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

相关文章

轻松掌握Java多线程 - 第四章:线程安全问题

学习目标1. 什么是线程安全1.1 线程安全的定义1.2 线程安全的重要性2. 共享资源访问的竞态条件2.1 什么是竞态条件2.2 竞态条件示例2.3 竞态条件的类型3. 线程安全问题的表现形式3.1...

如何在Java中实现线程安全?总结如下

在Java中,线程安全是指在多线程环境下,多个线程可以安全地访问共享资源或数据,而不会出现不一致或意外的结果。以下是一些实现线程安全的常用方法:1、使用synchronized关键字: 通过在方法或代...

Java多线程与锁机制详解:打造高效安全的并发世界

Java多线程与锁机制详解:打造高效安全的并发世界在当今这个数据处理量爆炸的时代,单线程程序已经难以满足高性能需求。Java作为一门优秀的编程语言,提供了强大的多线程支持,而锁机制正是保证多线程安全的...

Java集合框架的线程安全性:多线程编程的守护者

Java集合框架的线程安全性:多线程编程的守护者在Java的世界里,集合框架是所有开发者都绕不开的重要组成部分。无论是处理数据的存储还是操作,集合类几乎无处不在。然而,当我们把目光投向多线程编程的时候...

揭秘Java局部变量线程安全的真相:为什么它天生免疫并发问题

··在Java并发编程中,线程安全是一个永恒的话题。你是否曾疑惑:为什么局部变量不需要加锁就能避免并发问题?本文将深入剖析其底层原理,结合实战案例,带你彻底理解这一设计精髓。(点击收藏,解锁高薪面试必...

Java线程安全

当多个线程处理相同的数据,数据值发生变化时,会得到不一致的结果,这种情况不是线程安全的。 当一个线程已经在一个对象上工作并阻止另一个线程在同一个对象上工作时,这个过程称为线程安全。线程安全体现原子性:...