V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
monetto
V2EX  ›  Java

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

  •  
  •   monetto · 2019-08-23 18:08:36 +08:00 · 4019 次点击
    这是一个创建于 1917 天前的主题,其中的信息可能已经有所发展或是发生改变。

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

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

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

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

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

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

    Leiothrix
        1
    Leiothrix  
       2019-08-23 18:17:40 +08:00
    指令重排,保证命令的原子性
    sudden
        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 未初始化完全的对象。
    Duluku
        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
    lurenw
        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 完毕, 才能读取.
    Duluku
        5
    Duluku  
       2019-08-23 19:19:21 +08:00 via iPhone
    @lurenw 将某个对象声明为 volatile 之后、在这个对象被修改之后会立即写回、并将其他线程中所获得的该对象缓存全部宣布失效、强制所有的其他线程重新获取新的数值
    jieee
        6
    jieee  
       2019-08-23 19:21:22 +08:00
    volatile 保证可见性
    Raymon111111
        7
    Raymon111111  
       2019-08-23 19:29:47 +08:00
    可以看这个 https://www.cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.html

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

    Java Concurrency in Practice 這本書裏唯一一次出現 "infamous" 就是講這個模式爲什麼錯
    Kahnn
        12
    Kahnn  
       2019-08-23 20:39:10 +08:00
    前 3 楼说的对
    @monetto 没初始化完成的话,线程 2 怎么进入 Syn 内的呢?没初始化的话线程 2 不需要进入 Sync 里啊,直接 return uniqueSingleton 了
    mreasonyang
        13
    mreasonyang  
       2019-08-24 03:37:38 +08:00 via iPhone   ❤️ 1
    @monetto 对象本身的内存已经分配,此时对象就不是 null 了,所以在没有用 volatile 禁止指令重排时,其他线程走到第一个 if 判断后将认为对象不为空而不进入 sync 逻辑进而直接获取到对象。但此时对象的属性等数据可能还没初始化完成,这就会导致操作对象属性等数据时出现 NPE 或不符合预期的数据
    IamNotShady
        14
    IamNotShady  
       2019-08-24 11:48:39 +08:00
    学习了
    monetto
        15
    monetto  
    OP
       2019-08-24 15:28:06 +08:00
    @Kahnn
    @mreasonyang
    @momocraft
    谢谢大家,懂了
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   1052 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 23ms · UTC 22:35 · PVG 06:35 · LAX 14:35 · JFK 17:35
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.