以 双重检查单例模式为例,先上代码
public class Singleton {
public Singleton() {
}
private static volatile Singleton singleton;
public static Singleton getSingleton() {
if (singleton == null) {
synchronized (Singleton.class) {
if (singleton == null) {
singleton = new Singleton();
}
}
}
return singleton;
}
}
如上述代码所示,singleton 用了 volatile 修饰,而我们都知道 volatile 的作用
- 不能保证原子性
- 保证共享变量的可见性
- 禁止指令重排序
那么在单例模式中,volatile的作用到底是什么呢?
其实,在单例模式中,volatile的作用主要是 禁止指令重排序
那么为什么呢?
在上述代码中,singleton = new Singleton(); 可分解为以下步骤:
- 分配对象内存空间
- 初始化对象
- 设置 singleton 指向分配的内存地址
但是步骤2,3是可能交换的,也就是发生重排序,但是根据 Java语言规范 intra-thread semantics,它是允许那些在单线程内,不会改变单线程程序执行结果的重排序,也就是说,虽然步骤2,3发生重排序,但是对于单线程来说,初次访问对象时,其结果都是正确的,所以即使重排序也无所谓了。
但是在多线程环境中,这就会出现问题,例如,如下多线程发生重排序案例:
时间 | 线程A | 线程B |
---|---|---|
t1 | 分配对象空间 | |
t2 | 设置 singleton 指向分配的内存空间 | |
t3 | 判断 singleton 是否为空 | |
t4 | 由于 singleton 不为null,线程B将访问singleton 引用的对象 | |
t5 | 初始化对象 | |
t6 | 访问 singleton 引用的对象 |
有上述表格流程可以看出,在发生重排序的情况下,会导致线程B在 t3 时间下,判断出 singleton 不为null,那么线程B就会拿到这个 singleton 去做别的事,那么此时这个 singleton 没有初始化,那么就会报错,这就出了问题。
正常流程而言,在t2时间线程A应该对 singleton初始化,那么线程B在
可能会有小伙伴就差临门一脚的感觉就懂了,那我根据代码再说一遍
public class Singleton {
public Singleton() {
}
private static volatile Singleton singleton;
public static Singleton getSingleton() {
if (singleton == null) {
synchronized (Singleton.class) {
if (singleton == null) {
singleton = new Singleton();
}
}
}
return singleton;
}
}
根据代码而言,如果线程A在 第12行 即 singleton = new Singleton();发生重排序,那么线程B在 第9行即第一个if (singleton == null)处就有可能判断出 singleton不为null,进而拿到返回的 singleton 去做别的事进而导致报错。
讲到这里相信各位小伙伴差不多都懂了,如果还不懂可以多看两遍。
所以为了解决这个问题,就使用 volatile来修饰 singleton,来禁止重排序而可能出现的问题。
觉得不错的小伙伴可以一键三连哦!,感谢支持!!!
更多面试题请移步 大厂面试题专栏