zoukankan      html  css  js  c++  java
  • 07:线程安全-可见性问题

    由指令重排序引起的可见性问题:
    public class Test {
        // 如果运行时加上 -server 下面的代码就变成了死循环,没有加就正常运行。(运行器的编译优化只有在服务器模式下才执行)
        // 通过设置JVM参数,打印出JIT(即时编译)编译的内容(这里说的编译不是指class文件的编译,而是指未变级别的编译)
        private boolean flag = true;
       // -server -Djava.compiler=NONE 参数可以关闭jit优化。
        // 在多线程中,由于指令重排序引起的线程可见性问题。
        public static void main(String[] args) throws IOException, InterruptedException {
            Test demo1 = new Test();
            new Thread(new Runnable() {
                @Override
                public void run() {
                    int i = 0;
                    // class文件在运行时jit编译成为汇编指令,汇编指令出现了重排序。
                    /*
                    // 重排序后的逻辑。因为while语句里面需要一直判断flag。所以jvm优化为外层使用if判断一次。
                    if(demo1.flag){
                        while (true){
                            i++;
                        }
                    }  hot code : 热点代码。(就像是下面的while语句中的内容,执行频率很高。jvm认为是热点代码)
                    */
                    while (demo1.flag) {
                        i++;
                    }
                    System.out.println("i = " + i);
                }
            }).start();
            TimeUnit.SECONDS.sleep(2);
            // 设置flag为false,使上面的线程结束循环。
            demo1.flag = false;
            System.out.println("主线程结束");
        }
    }
    问题描述:
        多个CPU执行多个线程任务时,由于线程栈是独享的,如果共享数据在缓存中更新不及时,会出现多线程共享数据不一致。(基本不可能出现,因为现代计算机比较完善了)    
        运行时编译器JIT,将class编译称为汇编指令。在服务器模式下。会对执行的顺序进行优化重排序。但是放在多线程中执行时会由于执行的顺序改变执行的结果。
        由于指令进行了重排序。导致缓存的修改对本线程不可见。
        (指令重排序导致了while语句变成了if执行,之后在多线程的情况下。一个线程先判断if,另外一个线程后修改条件。所以if语句中的内容不会执行了。)
        (指令重排序是为了:提高CPU的使用率。一个线程阻塞的时候,别的线程继续执行。)
    Java官方文档:docs.oracle.com
    Java内存模型
    什么是内存模型:
    内存模型通过限定程序在某个点上是否可以读取某个值。内存模型用于描述程序运行的可能行为。从而可以根据内存模型对程序的运行结果进行准确的推断。
    Java内存模型的规范:(规范Jvm的重排序规则 )
    1:规范线程数据的存放:
    可以在线程之间共享的内存称为:共享内存或堆内存。(实例、静态字段、数组元素等)
    2:规范线程之间的操作:
    写数据、读数据、锁定、解锁、线程启动、线程终止、外部操作(socket操作等)
    3:规范线程同步规则:
    对同一把锁的解锁和加锁同步(就是说解锁后才可以加锁)、
    对volatile变量的写入和读取要同步(就是说,一个线程的写入之后别的才可以读到)、
    线程的启动操作和线程中的第一个操作同步(就是说:启动之后才可以执行操作)、
    等等等。
    都是为了保证数据可见性和一致性。
    4:Happens-before先行发生原则:强调两个有冲突动作的顺序,定义数据使用的时机。(我做的操作,对你是可见的。)
    某个管程上的unlock动作Happens-before同一个管程上的lock动作。(线程解锁了才能加锁)、
    某个线程对volatile字段的操作,对后续代码都可见、
    某个线程中的动作都Happens-before该线程后面的所有动作。(就是说线程的动作都知道前面执行了什么。)、
    等等。
    volatile关键字:
    加了volatile关键字的共享变量,会遵循happens-before规范、Jvm不会指令重排序、禁止缓存该变量。对该关键字的修改会可见于别的线程。
    没有缓存后,直接读取主存中的数据,所以各个线程之间数据是可见的。
    final在JVM中的处理:
    用在类上:是一个终极类。
    构造函数中设置final字段,只要别的线程可以获取到该对象,那么final字段一定是正确的。(如果不final,可能读到默认值。)
    如果在构造方法中set字段,然后后get该字段,如果该字段不是final的。有可能读取到默认值。
    例如:public finalDemo(x =1 ; y = x); 如果x是final的,y将=1;否则y可能为0;
    读取共享的final成员变量之前,要先获取到共享对象。这个操作不能重排序。
    了解内容:
    static final 是不可修改的字段。然而System.in、System.out、System.err都是static final的字段。但是可以通过set方法修改。(历史遗留原因)
    虚拟机规范里double类型的数据不是原子性的。例如读:先读32位,再读32位。但是 final double是原子的。(商业的jvm考虑到实际情况,一般多将double做成了原子的)
    
    
  • 相关阅读:
    python Unittest中setUp与setUpClass的区别
    Python的range和xrange的区别
    Python列表中的列表元素(嵌套列表)访问
    python字符串转为列表
    python正则匹配
    Python自动化测试用例设计--自动化测试用例与手工测试用例区别与联系
    Python+Selenium学习--自动化测试模型
    Python+Selenium学习--cookie处理
    Python+Selenium学习--控制浏览器控制条
    Python+Selenium学习--下载文件
  • 原文地址:https://www.cnblogs.com/Xmingzi/p/12601067.html
Copyright © 2011-2022 走看看