网上有人说是因为指令重排序
锁{ 1. 申请内存 2. 初始化为一个 Object 3. 将指针指向 Object }
但是执行是按照 1 3 2 执行的 造成没初始化完毕线程 2 进入 syn 锁内, 然后判断 null 成功, 又初始化了一个对象
但是我理解即使是按照 1 3 2 执行的,JVM 不也应该执行完全部代码才能释放 syn 吗?也就是说,2 过程没执行完毕呢这个锁也不可能被释放掉啊。
我个人感觉是因为 第一次检查的时候, 线程 2 缓存了这个对象, 然后因为没加 volatile, 第二次判断是否为 null 仍然是从其 CPU 缓存里面检查的, 加 volatile 是为了保证其每次都重新去内存里面取一下然后做判断。
麻烦大神解答一下,小弟感激不尽
1
Leiothrix 2019-08-23 18:17:40 +08:00
指令重排,保证命令的原子性
|
2
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 未初始化完全的对象。 |
3
Duluku 2019-08-23 19:11:42 +08:00 via iPhone
老哥,是这样如果是 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 |
4
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 完毕, 才能读取. |
5
Duluku 2019-08-23 19:19:21 +08:00 via iPhone
@lurenw 将某个对象声明为 volatile 之后、在这个对象被修改之后会立即写回、并将其他线程中所获得的该对象缓存全部宣布失效、强制所有的其他线程重新获取新的数值
|
6
jieee 2019-08-23 19:21:22 +08:00
volatile 保证可见性
|
7
Raymon111111 2019-08-23 19:29:47 +08:00
|
8
lurenw 2019-08-23 19:47:13 +08:00
@Duluku emmm... 你是要纠正我么, 你说的这个只是表现, 底层就是声言 LOCK, 锁 cache line 或 锁 bus.
|
9
monetto OP @sudden 仅有当执行完 1 2 3 之后才会释放掉 Syn 不是吗?没初始化完成的话,线程 2 怎么进入 Syn 内的呢?
|
11
momocraft 2019-08-23 20:27:04 +08:00
在不正確同步時你看到對象引用 (!= null) 不保證任何事
Java Concurrency in Practice 這本書裏唯一一次出現 "infamous" 就是講這個模式爲什麼錯 |
12
Kahnn 2019-08-23 20:39:10 +08:00
前 3 楼说的对
@monetto 没初始化完成的话,线程 2 怎么进入 Syn 内的呢?没初始化的话线程 2 不需要进入 Sync 里啊,直接 return uniqueSingleton 了 |
13
mreasonyang 2019-08-24 03:37:38 +08:00 via iPhone 1
@monetto 对象本身的内存已经分配,此时对象就不是 null 了,所以在没有用 volatile 禁止指令重排时,其他线程走到第一个 if 判断后将认为对象不为空而不进入 sync 逻辑进而直接获取到对象。但此时对象的属性等数据可能还没初始化完成,这就会导致操作对象属性等数据时出现 NPE 或不符合预期的数据
|
14
IamNotShady 2019-08-24 11:48:39 +08:00
学习了
|
15
monetto OP |