首页 新闻 会员 周边

Java单例双重验证不加volatile的问题为什么无法重现

0
悬赏园豆:200 [待解决问题]

如下所示,没有加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;
	}
}
友善的穿山甲兄弟的主页 友善的穿山甲兄弟 | 初学一级 | 园豆:104
提问于:2023-09-07 09:26

这是一种可能性很低的情况。但是不代表不会发生。想要重现的话,建议多试试吧

郭景伟Larva 6个月前

@郭景伟Larva: 已经试了很多很多很多很多遍了,没有问题。我感觉这可能是谣传。

友善的穿山甲兄弟 6个月前
< >
分享
所有回答(4)
1

你用单线程当然无法重现,你试试多线程就知道了。

南宫懿痕 | 园豆:298 (菜鸟二级) | 2023-09-07 10:54

感谢回复,多线程试了,没报错。

支持(0) 反对(0) 友善的穿山甲兄弟 | 园豆:104 (初学一级) | 2023-09-07 11:27

@友善的穿山甲兄弟: 不是报错问题,是数据不安全问题,不加volatile 你的数据在内存中更改就不能及时通知其他线程,也就是说主内存中值变了,但是各个线程中的内存变量值还是原来的,这时候线程修改的值或者逻辑就无意义了,因为是个错的值。 加了volatile ,内存中值改变了,会及时通知其他线程重新获取。

支持(0) 反对(0) 南宫懿痕 | 园豆:298 (菜鸟二级) | 2023-09-07 17:59

@友善的穿山甲兄弟: 还有,真搞不懂,为啥不明白就给别人点踩~~~~

支持(0) 反对(0) 南宫懿痕 | 园豆:298 (菜鸟二级) | 2023-09-07 18:00

@南宫懿痕: 也就是说单例双重验证不加volatile会导致创建多个对象是吗?和CPU指令重排没关系。

支持(0) 反对(0) 友善的穿山甲兄弟 | 园豆:104 (初学一级) | 2023-09-08 14:25

@南宫懿痕: 没用肯定点踩啊。

支持(0) 反对(0) 友善的穿山甲兄弟 | 园豆:104 (初学一级) | 2023-09-08 14:25
1

volatile 保证的是可见性,保证不了原子性,双检锁需要用到volatile,是用他的禁止指令重排序优化,java代码里的命令、运算不是原子操作,简单来说,加不加volatile,他们所生成汇编代码是有差别的。

你上述的代码使用多线程去测试,是测不出来的,单例双检锁如果因为没有加volatile导致new了多个对象,代码是不会报错,只是你new出来的对象不可用,是不完整的。

具体为什么不完整,你可以去查下资料,我简单理解就是:在第一个线程执行 instance = new Singleton();这块代码时候,如果没有加上volatile,那么第二个线程去调用getInstance()的时候,可能存在第一个线程还没执行结束,第二个线程在判断instance == null 为false。

让我发会呆 | 园豆:2929 (老鸟四级) | 2023-09-08 09:57

大佬,如何重现呢?我急需肉眼能够看到的东西。

支持(0) 反对(0) 友善的穿山甲兄弟 | 园豆:104 (初学一级) | 2023-09-08 14:23

@友善的穿山甲兄弟: 这种无论是你本地测试,甚至放到真正的生产环境都不一定会报错的,因为这本来就是很随机(概率很小)的事情

支持(0) 反对(0) 让我发会呆 | 园豆:2929 (老鸟四级) | 2023-09-08 14:30

@让我发会呆: 概率小到非常难测出来我懂,但有没有可能根本就没这个问题。

支持(0) 反对(0) 友善的穿山甲兄弟 | 园豆:104 (初学一级) | 2023-09-11 12:00

@友善的穿山甲兄弟: 这个不好说,因为确实没有测试复现过,你可以你看下《深入理解JVM虚拟机》这本书,里面有关于这方面的讨论,我本身是倾向于相信 volatile在禁止指令重排序优化这块的作用的

支持(0) 反对(0) 让我发会呆 | 园豆:2929 (老鸟四级) | 2023-09-11 16:50

但是sychronized就保证了new对象时候的原子性、可见性、和有序性了,volatile关键字是为了在保证静态变量在类加载时的有序性?

支持(0) 反对(0) 萌萌哒的鸡蛋饼 | 园豆:202 (菜鸟二级) | 2023-09-19 16:55

@萌萌哒的鸡蛋饼: sychronized是加锁,保证了同时只有一个线程去执行代码块,别的线程依然可以去调用getInstance方法以及去判断第一个 instance 是否等于 null,sychronized的加锁范围之外的

支持(1) 反对(0) 让我发会呆 | 园豆:2929 (老鸟四级) | 2023-09-19 17:26

@让我发会呆: 《深入理解JVM虚拟机》确实讲了volatile问题,他说加了volatile比没加volatile多了个lock addl $0x0,-0x40(%rsp)指令,但是我使用jdk11获得到的汇编结果,发现加不加volatile都有lock addl $0x0,-0x40(%rsp)指令。

支持(0) 反对(0) 友善的穿山甲兄弟 | 园豆:104 (初学一级) | 2023-10-18 15:59
1

帮你问了下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 或其他线程安全的初始化方式来保证单例的正确性。

橘足轻重1 | 园豆:202 (菜鸟二级) | 2023-09-15 15:35
0

可以试试jmeter压测工具多开几个线程调用试试看。希望能帮助你。

景伟·郭 | 园豆:183 (初学一级) | 2023-10-27 15:02
清除回答草稿
   您需要登录以后才能回答,未注册用户请先注册