zoukankan      html  css  js  c++  java
  • 重排序与内存语义

    重排序

    什么是重排序

    编译器和处理器为了提高程序的运行性能,对指令进行重新排序

    数据依赖性(as-if-serial)

    虽然重排序能够优化性能,但是前提是必须保证结果正确,那么它是如何在单线程下保证结果正确的呢?是根据诗句的依赖性来做的,数据的依赖性可以分为三类:

    • 写后读

      写后读的操作肯定是不能重排的,例如a = 1然后b = a

      这对于a来说是一个先写后读的过程,这个过程不能改变,如果变成b = a然后a = 1,那么结果也就变了,b最终得到的就不是1

    • 读后写

      读后写也不能重排,例如b = a然后a = 1,对于a来说这是一个读后写的操作,不能重排

      重排后变成a = 1,b = a。那么结果就变了,最终b的值就是1了

    • 写后写

      写后写也是不能重排的,例如a = 1,a = 2。

      如果重排,变成a = 2,a = 1。那么最后a的结果是1而不是2了

    指令重排序分类

    • 编译器重排序
    • 处理器重排序

    指令重排序带来的影响

    在单线程中不会有太大的影响,只会提升执行效率,但是在多线程中指令重排序会有较大的影响

    例如:

    public class Demo08 {
        private int a;
        private boolean flag;
    
        public void writer() {
            a = 1;
            flag = true;
        }
    
        public void read() {
            if (flag) {
                int b = a + 1;
                System.out.println(b);
            }
        }
    }
    

    在上面的代码中我们要做的事很明确,就是先将a设置为1,然后flag设置为true。然后再判断flag的值,这时候为true,会进if判断,然后b = a + 1,因此理想的情况下b等于2。

    1. 但是事实并非如此,因为a = 1与flag = true之间是没有数据的依赖性的,因此会进行重排序,很可能会先执行flag = true,那么这时候也会进if判断,但此时还没有执行a = 1,因此b的可能是0 + 1 = 1
    2. 还有可能int b = a + 1会先执行,然后将执行的结果放到某块内存,等到flag为true的时候再取出来,但是早在执行int b = a + 1的时候a的值可能还没改变,那就会有问题

    happens-before

    happens-beore是用来指定两个操作之间的执行顺序,提供跨线程的内存可见性

    happens-before规则如下:

    • 程序顺序规则
      • 单个线程中的每一个操作,总是前一个操作happens-before于该线程中任意后续操作,也就是说如果a happens-before b,那么并不是表示a操作在b操作之前执行,而是表示a操作对于b操作是可见的
    • 监视器锁规则
      • 对于一个锁的解锁总是happens-before于随后对这个锁的加锁
    • volatile变量规则
      • 对于一个volatile域的写,happens-before于任意后续对这个volatile域的读
    • 传递性
      • 如果a happens-before b,而b happens-before c,那么a一定happens-before c
    • Start规则
      • 如果在a线程中调用了b线程,那么在a线程中的所有代码是happens-before b线程的
    • Join规则
      • 如果在a线程中执行了b线程的join()方法,那么b线程happens-before a线程,这与start规则是相反的

    内存语义

    锁的内存语义

    锁的释放与获取所建立的happens-before关系:锁的释放happens-before锁的获取

    锁的释放和获取的内存语义:锁除了让临界区互斥执行外,还可以让释放锁的线程向获取同一个锁的线程和发送消息

    volatile内存语义

    volatile读写所建立的happens before关系:对于一个volatile域的写,happens-before于任意后续对这个volatile域的读

    volatile读写的内存语义:当写一个volatile变量时,Java内存模型会把该线程对应的本地内存中的共享变量刷新到主内存中;当读一个volatile变量时,Java内存模型会把当前线程对应的本地内存中的共享变量重置为无效,然后从主内存中读取共享变量

    final内存语义

    在java中,被final修饰的类不能被继承,被final修饰的方法不能被重写,被final修饰的变量不能被改变

    • 写final域的重排序规则:禁止把final域的写重排序到构造方法之外,也就是可以在构造方法中初始化final修饰的变量

      public class Demo09 {
          private final int b;
          public Demo09() {
              b = 10;
          }
      }
      
      // 编译器会在final域的写之后,在构造方法执行完毕之前,插入一个内存屏障StoreStore,保证处理器把final域的写操作在构造方法之中完成
      
      // 一般的内存屏障
      // LoadLoad
      // StoreStore
      // LoadStore
      // StoreLoad
      
    • 读final域的重排序规则:在一个线程中,初次读对象引用和初次读该对象所包含的final域,Java内存模型禁止处理器重排序这两个操作

    • final域为静态类型:

    • final域为抽象类型:在构造方法内对一个final引用的对象的成员域的写入,与随后在构造方法外把这个被构造对象的引用赋值给一个引用变量,这个两个操作之间不能重排序

  • 相关阅读:
    robotframework + python2.7.9 + selenium 2.44.0 + selenium2library1.7 测试环境搭建成功!
    SugarCRM安装踩雷(一)
    linux下安装xampp
    LoadRunner脚本回放与设置
    LoadRunner10自带的WEBTOURS,无法显示Flights页面问题解决办法
    重装系统后,重新搭建Selenium Server+Firefox环境
    LoadRunner 11中Record无法自动生成脚本——解决办法
    RFTWEB测试对象抓取的方法
    转载:如何使用RFT自动打开IE
    读《实战 GUI 产品的自动化测试》之:第四步,高阶技巧
  • 原文地址:https://www.cnblogs.com/Myarticles/p/12046101.html
Copyright © 2011-2022 走看看