Java 双锁 syn 为什么不加 volatile 就是非绝对安全的?

2019-08-23 18:08:36 +08:00
 monetto

网上有人说是因为指令重排序

锁{ 1. 申请内存 2. 初始化为一个 Object 3. 将指针指向 Object }

但是执行是按照 1 3 2 执行的 造成没初始化完毕线程 2 进入 syn 锁内, 然后判断 null 成功, 又初始化了一个对象

但是我理解即使是按照 1 3 2 执行的,JVM 不也应该执行完全部代码才能释放 syn 吗?也就是说,2 过程没执行完毕呢这个锁也不可能被释放掉啊。

我个人感觉是因为 第一次检查的时候, 线程 2 缓存了这个对象, 然后因为没加 volatile, 第二次判断是否为 null 仍然是从其 CPU 缓存里面检查的, 加 volatile 是为了保证其每次都重新去内存里面取一下然后做判断。

麻烦大神解答一下,小弟感激不尽

4044 次点击
所在节点    Java
15 条回复
Leiothrix
2019-08-23 18:17:40 +08:00
指令重排,保证命令的原子性
sudden
2019-08-23 19:03:10 +08:00
public class Singleton {
private static Singleton uniqueSingleton;

private Singleton() {
}

public Singleton getInstance() {
if (null == uniqueSingleton) {
synchronized (Singleton.class) {
if (null == uniqueSingleton) {
uniqueSingleton = new Singleton(); // error
}
}
}
return uniqueSingleton;
}
}

第一次 null 检查并没有在 syn 只内,所以线程 2 会访问到线程 1 未初始化完全的对象。
Duluku
2019-08-23 19:11:42 +08:00
老哥,是这样如果是 1 额我这个顺序、那么 3 执行完之后、这个 instance 就不是 null 了、那么这样在这一瞬间就有可能被别的线程取走这个 instance、但是这个 instance 还是空的、会爆 npe

public static Test getInstance() {
if (instance == null) {
synchronized (Test.class) {
if (instance == null) {
instance = new Test();
}
所以这个 instance 必须 volatile
lurenw
2019-08-23 19:14:01 +08:00
多年前看到一个解释(中文博客看到的,可能不正确)

DCL 安全问题的根源是, 初始化内存后 Object 就不为 null, 但是 Object 中的 Field 仍旧未被分配值. 此时其他线程就会判断 Object != null. 那么后续拿到的 field 就是未分配值的 field.

加了 volatile 之后, 就会 lock 住这个变量所在的缓存(可能 lock 总线, 也可能 lock cache line), 导致其他 cpu 不能访问. 需要等到更新 wirte 完毕, 才能读取.
Duluku
2019-08-23 19:19:21 +08:00
@lurenw 将某个对象声明为 volatile 之后、在这个对象被修改之后会立即写回、并将其他线程中所获得的该对象缓存全部宣布失效、强制所有的其他线程重新获取新的数值
jieee
2019-08-23 19:21:22 +08:00
volatile 保证可见性
Raymon111111
2019-08-23 19:29:47 +08:00
可以看这个 https://www.cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.html

主要还是指令重排的问题
lurenw
2019-08-23 19:47:13 +08:00
@Duluku emmm... 你是要纠正我么, 你说的这个只是表现, 底层就是声言 LOCK, 锁 cache line 或 锁 bus.
monetto
2019-08-23 20:14:58 +08:00
@sudden 仅有当执行完 1 2 3 之后才会释放掉 Syn 不是吗?没初始化完成的话,线程 2 怎么进入 Syn 内的呢?
monetto
2019-08-23 20:16:02 +08:00
@lurenw 难道 new instance 这个过程没有被完全执行完毕,Syn 就会被释放掉吗?
momocraft
2019-08-23 20:27:04 +08:00
在不正確同步時你看到對象引用 (!= null) 不保證任何事

Java Concurrency in Practice 這本書裏唯一一次出現 "infamous" 就是講這個模式爲什麼錯
Kahnn
2019-08-23 20:39:10 +08:00
前 3 楼说的对
@monetto 没初始化完成的话,线程 2 怎么进入 Syn 内的呢?没初始化的话线程 2 不需要进入 Sync 里啊,直接 return uniqueSingleton 了
mreasonyang
2019-08-24 03:37:38 +08:00
@monetto 对象本身的内存已经分配,此时对象就不是 null 了,所以在没有用 volatile 禁止指令重排时,其他线程走到第一个 if 判断后将认为对象不为空而不进入 sync 逻辑进而直接获取到对象。但此时对象的属性等数据可能还没初始化完成,这就会导致操作对象属性等数据时出现 NPE 或不符合预期的数据
IamNotShady
2019-08-24 11:48:39 +08:00
学习了
monetto
2019-08-24 15:28:06 +08:00
@Kahnn
@mreasonyang
@momocraft
谢谢大家,懂了

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/594586

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX