• java_乱序执行


    什么是乱序执行

      CPU运行的时候,是按照指令一条一条执行的。CPU速度特别快,但是CPU从内存去取数据的话,会很慢。这时候,就可能出现后来的指令要比先到的指令先执行的情况,例如:现在给CPU两条指令 ,两个指令没有关系。第一条指令从内存读数据。需要等待很长时间,那么在等待内存的过程中,会先执行指令2。然后等数据到了以后,然后执行指令1。看起来就是指令2 比指令1先执行。这些乱序执行,是CPU自动优化的结果,可以提高效率。而且如果第二条指令依赖第一条指令,不会发生指令乱序执行。

      乱序执行的测试代码:

    /**
     * 指令重排序验证,在这个案例代码中,可能出现场景如下:
     * 1.one线程执行完毕后执行other 线程结果:a=1,x=0;b=1,y=1; xy值为:01组合
     * 2.other线程执行完毕后执行one 线程结果:a=1,x=1;b=1,y=0; xy值为:10组合
     * 3.other线程和one线程同时执行 线程结果:a=1,x=1;b=1,y=1; xy值为:11组合
     * 按照正常逻辑,不可能出现 xy值为00的组合
     * 
     * 
     * 出现这种情况,只有一种可能,出现乱序执行了。
     * @author LYs
     *
     */
    public class T04_Disorder {
    
        private static int x = 0,y=0;
        private static int a= 0,b=0;
        
        public static void main(String[] args) throws Exception {
            int i=0;
            for (; ;) {
                i++;
                x=0;y=0;
                a=0;b=0;
                Thread one = new Thread(new Runnable() {
                    
                    @Override
                    public void run() {
                        a=1;
                        x=b;
                    }
                });
                Thread other = new Thread(new Runnable() {
                    
                    @Override
                    public void run() {
                        b=1;
                        y=a;
                    }
                });
                
                one.start();other.start();
                one.join();other.join();
                String result = "第"+i+"次("+x+","+y+")";
                if (x==0 && y==0) {
                    System.err.println(result);
                    break;
                }
            }
        }
    }

    乱序执行有可能带来的问题

      有时候乱序执行会导致一些问题,单线程情况下,好像基本上没有啥影响。但是如果是多线程的话,就有可能会导致一些问题出现。举例:我们在创建对象的时候, new方法会产生4条指令。举例说明:

    java代码

    public  class Test {
        int i = 8;
    }

    指令如下:

    1  NEW Test2  DUP
    3  INVOKESPECIAL Test.<init>()V
    4  ASTORE 1

      其中第一行是在内存中为对象分配一块空间,这时候int i 的值是默认值0 。第二行是将指针缓存下这个不需要理解不影响分析乱序执行问题。第三行指令执行操作,将8赋值给变量i 。第四行指令将该内存的对象引用赋值给栈内的引用。当有两个线程,线程1创建test对象,同时线程2访问该对象,如果不为空,就取出i的值去进行操作。这时如果指令第三行和第四行发生重排序就会出现问题:

       图片来源于马士兵线程课

      如图所示,在线程1创建对象并给i默认值为0的时候,将线程的对象引用赋值给了栈中的对象变量。这时,在对该对象做非空判断时得到的结果是该对象存在。线程2就会去取线程1new出来的对象,但是线程1并没有执行指令4 init的方法。线程2 取到的值为0 但是正常操作应该取到的值为8 这就是指令重排序带来的问题。

    指令重排序问题解决

      指令重排序问题的解决办法是内存屏障,实现方式为:在执行的时候,加一堵墙,墙两边的指令不允许相互之间发生重排序。内存屏障的实现是在CPU级别实现的。Intel硬件提供了一系列的内存屏障,主要有: 
    1. lfence,是一种Load Barrier 读屏障 
    2. sfence, 是一种Store Barrier 写屏障 
    3. mfence, 是一种全能型的屏障,具备ifence和sfence的能力 
    4. Lock前缀,Lock不是一种内存屏障,但是它能完成类似内存屏障的功能。Lock会对CPU总线和高速缓存加锁,可以理解为CPU指令级的一种锁。

    java代码想要实现内存屏障,只需要在相应的变量前添加volatile关键字。这个关键字的作用为:1.保证可见性,2.防止指令重排序。jvm实现防止内存重排序使用的办法是锁定cpu总线,该办法效率较低,但是兼容性比较好。

  • 相关阅读:
    Java并发组件三之Semaphore
    Java并发组件二之CyclicBarriar
    Java并发组件一之CountDownLatch
    高并发多线程二
    多线程与高并发需要体会记住的点
    Maven 知识点总结以及解决jar报冲突的几种方法
    JAVA SSM整合流程以及注意点
    SpringMVC Tomcat 启动时报错:java.lang.IllegalStateException: Error starting child
    java.lang.ClassNotFoundException: org.springframework.web.filter.CharacterEncodingFilter
    javax.servlet.ServletException: No adapter for handler
  • 原文地址:https://www.cnblogs.com/liyasong/p/memory_barrier.html
Copyright © 2020-2023  润新知