请教大家一个 Java volatile 可见性问题 - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
songche
V2EX    Java

请教大家一个 Java volatile 可见性问题

  •  
  •   songche 2023-05-09 16:07:13 +08:00 2570 次点击
    这是一个创建于 894 天前的主题,其中的信息可能已经有所发展或是发生改变。

    以下摘自某教程:

    编译优化带来的有序性问题

    有序性指的是程序按照代码的先后顺序执行。而编译器为了优化性能,有时候会改变程序中语句的先后顺序。

    Java 中经典的案例就是利用双重检查创建单例对象,其中 volatile 就是保证有序性的。

    public class Sigleton { private static volatile Singleton singleton; private Singleton() { } public static Singleton getInstance() { if (singleton == null) { synchronized (Singleton.class) { if (singleton == null) { singleton = new Singleton(); } } } return singleton; } } 

    如果没有 volatile ,我们以为的 new 操作应该是:

    1. 分配一块内存 M ;
    2. 在内存 M 上初始化 Singleton 对象;
    3. 然后 M 的地址赋值给 instance 变量。

    但是实际上优化后的执行路径却是这样的:

    1. 分配一块内存 M ;
    2. 将 M 的地址赋值给 instance 变量;
    3. 最后在内存 M 上初始化 Singleton 对象。

    假设线程 A 先执行 getInstance() 方法,当执行完指令 2 时恰好发生了线程切换,切换到了线程 B 上;如果此时线程 B 也执行 getInstance() 方法,那么线程 B 在执行第一个判断时会发现 instance != null ,所以直接返回 instance ,而此时的 instance 是没有初始化过的,如果我们这个时候访问 instance 的成员变量就可能触发空指针异常。

    图片

    问题:线程 A 在 new 之前获取了锁,为啥线程 B 还可以访问?

    查资料有人说经过这两步 1.分配一块内存 M ; 2. 将 M 的地址赋值给 instance 变量; 后就会释放锁,不知道对不对

    第 1 条附言    2023-05-09 17:41:35 +08:00
    感谢大家,已解决,对 synchronized 理解有问题
    16 条回复    2023-05-09 17:31:46 +08:00
    strayerxx
        1
    strayerxx  
       2023-05-09 16:17:49 +08:00   1
    B 又没进入 synchronized 不需要获取锁,为什么不可以访问
    songche
        2
    songche  
    OP
       2023-05-09 16:24:38 +08:00
    @strayerxx 我理解的是 这个 instance 加了 synchronized 锁,那其他线程 B 不就不能访问了嘛。
    参考的:synchronized 通过当前线程持有对象锁,从而拥有访问权限,而其他没有持有当前对象锁的线程无法拥有访问权限,保证在同一时刻,只有一个线程可以执行某个方法或者某个代码块,从而保证线程安全。
    strayerxx
        3
    strayerxx  
       2023-05-09 16:31:43 +08:00
    @songche 如果是这样随便在一个地方加锁,其他地方都不能访问了,那设计 JUC 的那些大神为什么一门心思的将锁细化,那直接把 synchronized 加到方法上连 double check 都不需要了
    skyemin
        4
    skyemin  
       2023-05-09 16:31:43 +08:00
    @songche 锁的是代码块,if (singleton == null)在代码块之外
    strayerxx
        5
    strayerxx  
       2023-05-09 16:33:49 +08:00
    @songche 可以理解一下单例模式中的懒汉式和 DCL 的区别
    jambo
        6
    jambo  
       2023-05-09 16:43:46 +08:00   2
    @songche 如果你是 Java 的初学者, 不建议在这里花太多时间; 如果你在研究并发编程部分, 建议花点时间看下 Java 内存模型(jsr133), 特别是 jsr133 faq. 这个例子就是 jsr133 faq 里的: https://www.cs.umd.edu/~pugh/java/memoryModel/jsr-133-faq.html#dcl
    xiang0818
        7
    xiang0818  
       2023-05-09 17:01:17 +08:00
    public class Singleton {
    private static Singleton singleton;
    private Singleton() {
    }
    public static Singleton getInstance() {
    if (singleton == null) {. // 不加 volatile ,线程 B 这行代码会有问题,回取到未初始化的数据
    synchronized (Singleton.class) {
    if (singleton == null) {
    singleton = new Singleton(); // A 在这里 ,这里 M 的地址已经给了 singleton ,但是还没有初始化
    }
    }
    }
    return singleton;
    }
    }
    jtwor
        8
    jtwor  
       2023-05-09 17:03:01 +08:00
    "instance != null ,所以直接返回 instance ,而此时的 instance 是没有初始化过的,如果我们这个时候访问 instance 的成员变量就可能触发空指针异常"

    不懂 java ,有一个疑惑这里 instance 都不为空了怎么可能空引用。
    jambo
        9
    jambo  
       2023-05-09 17:07:35 +08:00
    @jtwor instance 本身不为 null, 是它指向的那个对象的属性没有完成初始化, 访问这些属性的时候可能抛空指针
    leonshaw
        10
    leonshaw  
       2023-05-09 17:08:41 +08:00
    op 没搞清锁是做什么的,加锁是阻止另一个线程加锁,不是阻止所有对对象的访问。同步操作一般需要双方配合,包括 volatile 也是隐含了读写配对。
    gaifanking
        11
    gaifanking  
       2023-05-09 17:09:03 +08:00   1
    标题写的可见性,内容却是说的有序性,这是两个问题。
    volatile 可以阻止重排序,这个没毛病。
    楼主的问题 1 楼已经回答了,这里锁的不是方法而是代码段。如果锁方法根本不需要 double check

    1 if (singleton == null) {
    2 synchronized (Singleton.class) {
    3 if (singleton == null) {
    4 singleton = new Singleton();
    5 }
    6 }
    7 }
    线程 A 在第 4 行执行,不影响线程 B 进入第 1 行
    yule111222
        12
    yule111222  
       2023-05-09 17:14:09 +08:00   1
    @jtwor 去看 6 楼的链接吧,说得很清楚。引用赋值和对象初始化是 2 条机器指令,再当前的 JMM 模型下对这 2 条指令做重排序是完全允许的,也就是可以在没有完成构建初始化的情况就给引用赋值了。所以线程 B 可能会拿到尚未初始化完成的对象,这个时候使用这个对象是非常危险的
    jtwor
        13
    jtwor  
       2023-05-09 17:24:33 +08:00
    @yule111222 原来如此,谢谢大佬。我是写.net 的,感觉就是内存屏障问题,主要我们这边的 lock 锁和 volatile 都会处理。 [也就是可以在没有完成构建初始化的情况就给引用赋值了] 这种情况真没听过
    oldshensheep
        14
    oldshensheep  
       2023-05-09 17:24:54 +08:00
    6 楼的链接里有很多有用的东西,你那个文章说的是对的,一楼解释是对的。
    虽然 instance!=null ,但是 instance 并没有初始化,仅仅是分配了内存。
    gaifanking
        15
    gaifanking  
       2023-05-09 17:27:30 +08:00
    @yule111222 请教下这个未初始化的对象使用的适合抛的什么异常呢?应该不是空指针吧,指针毕竟赋值了
    yule111222
        16
    yule111222  
       2023-05-09 17:31:46 +08:00
    @gaifanking 使用这个对象里面的属性可能会空指针,因为还没有初始化。
    关于     帮助文档     自助推广系统     博客     API     FAQ     Solana     907 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 27ms UTC 19:18 PVG 03:18 LAX 12:18 JFK 15:18
    Do have faith in what you're doing.
    ubao msn snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86