如下所示,没有加volatile的单例双重验证,我实验了无数遍都无法重现任何问题。
如果有大佬重现过,教教我如何重现。
有没有可能根本没有这个问题。
public class Singleton {
private static Singleton instance;
private Singleton() {}
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
你用单线程当然无法重现,你试试多线程就知道了。
感谢回复,多线程试了,没报错。
@友善的穿山甲兄弟: 不是报错问题,是数据不安全问题,不加volatile 你的数据在内存中更改就不能及时通知其他线程,也就是说主内存中值变了,但是各个线程中的内存变量值还是原来的,这时候线程修改的值或者逻辑就无意义了,因为是个错的值。 加了volatile ,内存中值改变了,会及时通知其他线程重新获取。
@友善的穿山甲兄弟: 还有,真搞不懂,为啥不明白就给别人点踩~~~~
@南宫懿痕: 也就是说单例双重验证不加volatile会导致创建多个对象是吗?和CPU指令重排没关系。
@南宫懿痕: 没用肯定点踩啊。
volatile 保证的是可见性,保证不了原子性,双检锁需要用到volatile,是用他的禁止指令重排序优化,java代码里的命令、运算不是原子操作,简单来说,加不加volatile,他们所生成汇编代码是有差别的。
你上述的代码使用多线程去测试,是测不出来的,单例双检锁如果因为没有加volatile导致new了多个对象,代码是不会报错,只是你new出来的对象不可用,是不完整的。
具体为什么不完整,你可以去查下资料,我简单理解就是:在第一个线程执行 instance = new Singleton();这块代码时候,如果没有加上volatile,那么第二个线程去调用getInstance()的时候,可能存在第一个线程还没执行结束,第二个线程在判断instance == null 为false。
大佬,如何重现呢?我急需肉眼能够看到的东西。
@友善的穿山甲兄弟: 这种无论是你本地测试,甚至放到真正的生产环境都不一定会报错的,因为这本来就是很随机(概率很小)的事情
@让我发会呆: 概率小到非常难测出来我懂,但有没有可能根本就没这个问题。
@友善的穿山甲兄弟: 这个不好说,因为确实没有测试复现过,你可以你看下《深入理解JVM虚拟机》这本书,里面有关于这方面的讨论,我本身是倾向于相信 volatile在禁止指令重排序优化这块的作用的
但是sychronized就保证了new对象时候的原子性、可见性、和有序性了,volatile关键字是为了在保证静态变量在类加载时的有序性?
@萌萌哒的鸡蛋饼: sychronized是加锁,保证了同时只有一个线程去执行代码块,别的线程依然可以去调用getInstance方法以及去判断第一个 instance 是否等于 null,sychronized的加锁范围之外的
@让我发会呆: 《深入理解JVM虚拟机》确实讲了volatile问题,他说加了volatile比没加volatile多了个lock addl $0x0,-0x40(%rsp)指令,但是我使用jdk11获得到的汇编结果,发现加不加volatile都有lock addl $0x0,-0x40(%rsp)指令。
帮你问了下gpt。它只提供了建议,没提供证据。
问题的关键点涉及到Java内存模型和多线程并发编程。在特定条件下,未加 volatile 关键字的双重检查锁定(Double-Checked Locking)可能导致线程安全问题。然而,这种问题可能受多个因素影响,包括硬件、JVM 实现、编译器优化等,使得很难在每一种环境下都准确地复现。
在多线程环境下,由于指令重排序和内存可见性等问题,未加 volatile 关键字可能导致在某些情况下,一个线程看到了一个还未完全初始化的对象,从而引发错误。
双重检查锁定的典型代码如下:
public class Singleton {
private static Singleton instance;
private Singleton() {}
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
这里的 instance 并没有加 volatile 关键字。实际上,目前的JVM实现(至少在大多数情况下)已经能够正确处理双重检查锁定,因此你可能很难在现代的JVM上复现该问题。
如果你想尝试复现这个问题,你可以尝试在不同的JVM实现、不同的硬件平台、不同的JVM参数设置等情况下运行你的代码,以及增加更多的并发情况。另外,可以尝试使用低版本的JDK,因为一些早期版本的JDK可能存在问题。
总的来说,由于现代JVM对指令重排序和内存模型有很好的处理,可能很难在普通情况下准确地复现这个问题。然而,在多线程环境下,仍然建议使用 volatile 或其他线程安全的初始化方式来保证单例的正确性。
可以试试jmeter压测工具多开几个线程调用试试看。希望能帮助你。
这是一种可能性很低的情况。但是不代表不会发生。想要重现的话,建议多试试吧
– 郭景伟Larva 1年前@郭景伟Larva: 已经试了很多很多很多很多遍了,没有问题。我感觉这可能是谣传。
– 友善的穿山甲兄弟 1年前