大家好,又见面了,我是你们的朋友全栈君。如果您正在找激活码,请点击查看最新教程,关注关注公众号 “全栈程序员社区” 获取激活教程,可能之前旧版本教程已经失效.最新Idea2022.1教程亲测有效,一键激活。
Jetbrains全系列IDE稳定放心使用
单例模式写法有很多,于是我看到了这么一种写法:
public class SingletonTest {
private SingletonTest() {
}
private static SingletonTest singletonTest = null;
public static SingletonTest getSingletonTest() {
if (singletonTest == null) {
// 若singletonTest为空,则加锁,再进一步判空
synchronized (SingletonTest.class) {
// 再判断一次是否为null
if (singletonTest == null) {
//若为空,则创建一个新的实例
singletonTest = new SingletonTest();
}
}
}
return SingletonTest;
}
}
这种写法算是一个考虑比较得当的设计了 为了防止多线程调用产生多个实例,采用了同步锁 加锁位置得当,尽可能降低了加锁对性能的影响
但是在这个示例下方,有指出可能会由于指令重排的影响,导致代码执行错误,只是概率很低。
我不由得重新审视着这段代码,难道看似稳的一逼的代码如此不堪一击?
于是,我大致了解了下指令重排:
指令重排序是JVM为了优化指令,提高程序运行效率,在不影响单线程程序执行结果的前提下,尽可能地提高并行度。
也就是说,JVM为了执行效率会将指令进行重新排序,但是这种重新排序不会对单线程程序产生影响。
首先,JVM是如何保证单线程下的指令在重新排序后执行结果不受影响的呢?
(此处省略一万字……关于happens-before,感兴趣的请看原文)
由于singletonTest = new SingletonTest()操作并不是一个原子性指令,会被分为多个指令:
memory = allocate(); //1:分配对象的内存空间
ctorInstance(memory); //2:初始化对象
instance = memory; //3:设置instance指向刚分配的内存地址
但是经过重排序后如下:
memory = allocate(); //1:分配对象的内存空间
instance = memory; //3:设置instance指向刚分配的内存地址,此时对象还没被初始化
ctorInstance(memory); //2:初始化对象
若有A线程进行完重排后的第二步,且未执行初始化对象。此时B线程来取singletonTest时,发现singletonTest不为空,于是便返回该值,但由于没有初始化完该对象,此时返回的对象是有问题的。这也就是为什么说看似稳的一逼的代码,实则不堪一击。
另外,在《java并发编程实战》16.2.4中对该种双重检查加锁(DCL)提出了批评。批评的主要点在于,该方式会导致上述指出的取到一个无效或错误状态的对象。
上述代码的改进方法:将singletonTest声明为volatile类型即可(volatile有内存屏障的功能)。
参考博客: https://www.cnblogs.com/senlinyang/p/7875458.html
节选自:https://blog.csdn.net/yuruixin_china/article/details/80550209
发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/180992.html原文链接:https://javaforall.cn
【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛
【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...