单例模式实现—考虑线程安全性

在设计模式中,最为常见常用的应该就是单例模式了。

单例模式的使用场景:

1.资源共享的情况下,避免由于资源操作时导致的性能或损耗等。如日志文件,应用配置。
2.控制资源的情况下,方便资源之间的互相通信。如线程池等。

单例模式要素:
  • 私有构造方法
  • 私有静态成员变量,自身类型的实例
  • 以自己实例为返回值的公有静态方法
单例模式常见的七种实现方式

——
1. 简单的懒汉模式 非线程安全

public class Sigleton1 {
    private static Sigleton1 instance;

    private Sigleton1() {
    }

    public static Sigleton1 getInstance() {
        if (instance == null) { //代码1 多线程环境下,判断是否为null这里是非线程安全的
            instance = new Sigleton1(); //代码2
        }
        return instance;
    }
}

假设线程A先判断 代码1 发现 instance 为 null 然后执行 代码2,此时线程B 执行代码1 但是线程A还没有完成对象的初始化,instance 此时为 null ,因此线程B 也会执行代码2 ,所以这不是线程安全的方法。
——
2. 采用同步的懒汉模式 线程安全

public class Sigleton2 {
    private static Sigleton2 instance;

    private Sigleton2() {
    }

    public synchronized static Sigleton2 getInstance() {
        if (instance == null) {
            instance = new Sigleton2();
        }
        return instance;
    }
}

在 getInstance() 方法加同步,线程安全性得到了保证,但是如果在多个线程频繁调用该方法的场景下,程序执行的性能会因为这里的同步而大幅下降。
——
3. 简单双重校验 非线程安全
采用双重校验理论上降低了同步的开销

public class Sigleton3 {
    private static Sigleton3 instance;

    private Sigleton3() {
    }

    public static Sigleton3 getInstance() {
        if (instance == null) {
            synchronized (Sigleton3.class) {
                if (instance == null) {
                    instance = new Sigleton3();
                }
            }
        }
        return instance;
    }
}

编译器提示安全问题:
这里写图片描述
原因是:

instance = new Sigleton3();

创建一个对象的过程可以分解为:

memory = allocate(); //1: 分配对象的内存空间
ctorInstance(memory); //2: 初始化对象
instance = memory; //3: 设置 instance 指向刚分配的内存地址

然而 代码2 和 代码3 的顺序可能会被编译器(如JIT)优化重排序,从而变成下面的执行顺序:

memory = allocate(); //1: 分配对象的内存空间
instance = memory; //3: 设置 instance 指向刚分配的内存地址
//注意对象还没有被初始化
ctorInstance(memory); //2: 初始化对象
时间 线程A 线程B
t1 A1:分配对象的内存空间
t2 A3:设置instance指向内存空间
t3 B1:判断instance是否为空
t4 B2:instance != null, 线程B将访问instance引用的对象
t5 A2:初始化对象
t6 A4:访问instance引用的对象

因此,上述的简单双重校验是危险的非线程安全实现方式。
——
4. 带volatile 的双重校验 线程安全
想把非线程安全的双重校验改成线程安全的双重校验有两种方式:
(1)禁止 2 和 3 的重排序
(2)允许 2 和 3 的重排序,但不允许其他线程“看到” 这个重排序。
基于上述第一个方法,只需要对实现3的简单实现做简单修改,对instance加上volatile即可:

public class Sigleton3 {
    private volatile static Sigleton3 instance;
// volatile 禁止对volatile写和volatile写前面任意内存操作重排序
// volatile 禁止对volatile读和volatile读后面任意内存操作重排序
// volatile 将修改后的值立刻刷新到内存,保证内存可见性
    private Sigleton3() {
    }

    public static Sigleton3 getInstance() {
        if (instance == null) {
            synchronized (Sigleton3.class) {
                if (instance == null) {
                    instance = new Sigleton3();
                }
            }
        }
        return instance;
    }
}

——
5. 饿汉模式 线程安全

public class Sigleton5 {
    private static Sigleton5 instance = new Sigleton5();

    private Sigleton5() {}

    public static Sigleton5 getInstance() {
        return instance;
    }
}

因为没有重复判断 instance 所以不存在线程安全性问题。
——
6. 基于类初始化的解决方案——静态内部类 线程安全 推荐

public class Sigleton6 {
    private Sigleton6() { }

    private static class SigletonHolder {
        private static final Sigleton6 instance = new Sigleton6();
    }

    public static Sigleton6 getInstance() {
        return SigletonHolder.instance;
    }
}

——
7. 基于枚举的单例模式 线程安全

public enum Sigleton {
    instance;
}

Over,再也不怕面试官让手撸单例模式了!

猜你喜欢

转载自blog.csdn.net/MachineRandy/article/details/81812707
今日推荐