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做成了原子的)
    
    
  • 相关阅读:
    HDU 1010 Tempter of the Bone(DFS剪枝)
    HDU 1013 Digital Roots(九余数定理)
    HDU 2680 Choose the best route(反向建图最短路)
    HDU 1596 find the safest road(最短路)
    HDU 2072 单词数
    HDU 3790 最短路径问题 (dijkstra)
    HDU 1018 Big Number
    HDU 1042 N!
    NYOJ 117 求逆序数 (树状数组)
    20.QT文本文件读写
  • 原文地址:https://www.cnblogs.com/Xmingzi/p/12601067.html
Copyright © 2011-2022 走看看