• happens-before原则


    一、问题

    一下代码经测试,打开注释行,子线程就不会陷入while死循环了,为什么呢

    public class VolatileTest3   {
        // b使用volatile修饰
        public static volatile long b = 0;
        //消除缓存行的影响
        public static long a1,a2,a3,a4,a5,a6,a7,a8;
        // c不使用volatile修饰
        public static long c = 0;
    
        public static void main(String[] args) throws InterruptedException {
            new Thread(()->{
                while (c == 0) {
                    //long x = b;
                }
                System.out.println("c=" + c);
            }).start();
    
            Thread.sleep(100);
    
            b = 1;
            c = 1;
        }
    }

             可以理解为:如果不加volatile,java编程语言的java memory model允许一个线程读到另一个线程任何一次写进去的值(可以是初值0也可以是主线程写入的1),只要不是happens-after它的就可以。但这个程序两个线程没有任何同步,所以没有任何happens-before关系。所以,就算主线程写,另一个线程永远读到c == 0,也是允许的。只要允许,你看到的“程序永远退不出去”就是合理的结果。至于为什么会出现这种现象,你暂且认为是巧合吧,反正这是《Java语言标准》允许的,JVM没做错什么。
             但是一旦加上volatile,所有线程对c的读写操作就构成一个序列。因为main早晚会执行完,所以早晚会又一个对c的写操作,写入1。由于new thread会不断读c,早晚会有一次读happens after那个往c里写1的操作。对于volatile变量来说,写之后的读都能看到那个写的值“1”。所以那个new thread早晚可以看到c == 1。

             或者可以理解为:共享变量c被两个线程读写,cpu缓存行存在两份值,如果不加volatile则更新过后的c的值不知道什么时候会更新到主存,加了volatile后会立即同步到主存,缓存行无效,另一个线程会重新从主存加载新值到cpu缓存行。

    二、关于happens-before原则

        

            Java内存模型中的happens-before是什么?为什么会有这东西的存在?

            其实我们学习的初期或者时间很急迫的时候我们都是死记硬背,没有理解这东西背后的含义和为什么需要这东西。没办法比如项目赶,一个新东西肯定是上手先,但是等我们空下来回过头来,我们还是需要去理解这些知识,只有这样我才能深刻的记住,并且运用熟练。

             happens-before字面翻译过来就是先行发生,A happens-before B 就是A先行发生于B?

    不准确!在Java内存模型中,happens-before 应该翻译成:前一个操作的结果可以被后续的操作获取。讲白点就是前面一个操作把变量a赋值为1,那后面一个操作肯定能知道a已经变成了1。

    我们再来看看为什么需要这几条规则?

    因为我们现在电脑都是多CPU,并且都有缓存,导致多线程直接的可见性问题。详情可以看我之前的文章面试官:你知道并发Bug的源头是什么吗?

    所以为了解决多线程的可见性问题,就搞出了happens-before原则,让线程之间遵守这些原则。编译器还会优化我们的语句,所以等于是给了编译器优化的约束。不能让它优化的不知道东南西北了!

       

    咱们来看看这几条规则

            程序次序规则:在一个线程内一段代码的执行结果是有序的。就是还会指令重排,但是随便它怎么排,结果是按照我们代码的顺序生成的不会变!

    管程锁定规则:就是无论是在单线程环境还是多线程环境,对于同一个锁来说,一个线程对这个锁解锁之后,另一个线程获取了这个锁都能看到前一个线程的操作结果!(管程是一种通用的同步原语,synchronized就是管程的实现)

             volatile变量规则:就是如果一个线程先去写一个volatile变量,然后一个线程去读这个变量,那么这个写操作的结果一定对读的这个线程可见。

      线程启动规则:在主线程A执行过程中,启动子线程B,那么线程A在启动子线程B之前对共享变量的修改结果对线程B可见。

      线程终止规则:在主线程A执行过程中,子线程B终止,那么线程B在终止之前对共享变量的修改结果在线程A中可见。

      线程中断规则:对线程interrupt()方法的调用先行发生于被中断线程代码检测到中断事件的发生,可以通过Thread.interrupted()检测到是否发生中断。

      传递规则:这个简单的,就是happens-before原则具有传递性,即A happens-before B , B happens-before C,那么A happens-before C。

      对象终结规则:这个也简单的,就是一个对象的初始化的完成,也就是构造函数执行的结束一定 happens-before它的finalize()方法。

  • 相关阅读:
    【译】.NET 的新的动态检测分析
    【译】Visual Studio 的 Razor 编辑器的改进
    【译】.NET 5. 0 中 Windows Form 的新特性
    MySQL InnoDB 索引(Index)
    MySQL 全文检索(Full-Text Search)
    MySQL 计算最大值、最小值和中位数
    MySQL 触发器(Triggers)
    MySQL 视图(View)
    MySQL基础知识:MySQL String 字符串处理
    MySQL基础知识:MySQL Connection和Session
  • 原文地址:https://www.cnblogs.com/yangcao/p/11672208.html
Copyright © 2020-2023  润新知