小知识,大挑战!本文正在参与“程序员必备小知识”创作活动
Code皮皮虾 一个沙雕而又有趣的憨憨少年,和大多数小伙伴们一样喜欢听歌、游戏,当然除此之外还有写作的兴趣,emm...,日子还很长,让我们一起加油努力叭🌈
✨作用
以 双重检查单例模式为例,先上代码
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,来禁止重排序而可能出现的问题。
😉精选专栏
毛遂自荐,给大家推荐一下自己的专栏😁,欢迎小伙伴们收藏关注😊
❤最后
我是 Code皮皮虾,一个热爱分享知识的 皮皮虾爱好者,未来的日子里会不断更新出对大家有益的博文,期待大家的关注!!!
创作不易,如果这篇博文对各位有帮助,希望各位小伙伴可以==一键三连哦!==,感谢支持,我们下次再见~~~