指令重排序、内存屏障很难?看完这篇你就懂了!

听说微信搜索《Java鱼仔》会变更强哦!

本文收录于JavaStarter ,里面有我完整的Java系列文章,学习或面试都可以看看哦

面试官在问到多线程编程的时候,指令重排序、内存屏障经常会被提起。如果你对这两者有一定的理解,那这就是你的加分项。

(一)什么是指令重排序

为了使处理器内部的运算单元能尽量被充分利用,处理器可能会对输入的代码进行乱序执行优化,处理器会在计算之后将乱序执行的结果重组,并确保这一结果和顺序执行结果是一致的,但是这个过程并不保证各个语句计算的先后顺序和输入代码中的顺序一致。这就是指令重排序。

简单来说,就是指你在程序中写的代码,在执行时并不一定按照写的顺序。

在Java中,JVM能够根据处理器特性(CPU多级缓存系统、多核处理器等)适当对机器指令进行重排序,最大限度发挥机器性能。

Java中的指令重排序有两次,第一次发生在将字节码编译成机器码的阶段,第二次发生在CPU执行的时候,也会适当对指令进行重排。

(二)复现指令重排序

光靠说不容易看出现象,下面来看一段代码,这段代码网上出现好多次了,但确实很能复现出指令重排序。我把解释放在代码后面。

public class VolatileReOrderSample {
    //定义四个静态变量
    private static int x=0,y=0;
    private static int a=0,b=0;

    public static void main(String[] args) throws InterruptedException {
        int i=0;
        while (true){
            i++;
            x=0;y=0;a=0;b=0;
            //开两个线程,第一个线程执行a=1;x=b;第二个线程执行b=1;y=a
            Thread thread1=new Thread(new Runnable() {
                @Override
                public void run() {
                    //线程1会比线程2先执行,因此用nanoTime让线程1等待线程2 0.01毫秒
                    shortWait(10000);
                    a=1;
                    x=b;
                }
            });
            Thread thread2=new Thread(new Runnable() {
                @Override
                public void run() {
                    b=1;
                    y=a;
                }
            });
            thread1.start();
            thread2.start();
            thread1.join();
            thread2.join();
            //等两个线程都执行完毕后拼接结果
            String result="第"+i+"次执行x="+x+"y="+y;
            //如果x=0且y=0,则跳出循环
            if (x==0&&y==0){
                System.out.println(result);
                break;
            }else{
                System.out.println(result);
            }
        }
    }
    //等待interval纳秒
    private static void shortWait(long interval) {
        long start=System.nanoTime();
        long end;
        do {
            end=System.nanoTime();
        }while (start+interval>=end);
    }
}

这段代码虽然看着长,其实很简单,定义四个静态变量x,y,a,b,每次循环时让他们都等于0,接着用两个线程,第一个线程执行a=1;x=b;第二个线程执行b=1;y=a。

这段程序有几个结果呢?从逻辑上来讲,应该有3个结果:

当第一个线程执行到a=1的时候,第二个线程执行到了b=1,最后x=1,y=1

当第一个线程执行完,第二个线程才刚开始,最后x=0,y=1

当第二个线程执行完,第一个线程才开始,最后x=1,y=0

理论上无论怎么样都不可能x=0,y=0;

但是当程序执行到几万次之后,竟然出现了00的结果:

在这里插入图片描述

这就是因为指令被重排序了,x=b先于a=1执行,y=a先于b=1执行。

在这里插入图片描述

(三)通过什么方式禁止指令重排序?

Volatile通过内存屏障可以禁止指令重排序,内存屏障是一个CPU的指令,它可以保证特定操作的执行顺序。

内存屏障分为四种:

StoreStore屏障、StoreLoad屏障、LoadLoad屏障、LoadStore屏障。

JMM针对编译器制定了Volatile重排序的规则:

在这里插入图片描述

光看这些理论可能不容易懂,下面我就用通俗的话语来解释一下:

首先是对四种内存屏障的理解,Store相当于是写屏障,Load相当于是读屏障。

比如有两行代码,a=1;x=2;并且我把x修饰为volatile。

执行a=1时,它相当于执行了一次普通的写操作;

执行x=2时,它相当于执行了一次volatile的写操作;

因此在这两行命令之间,就会插入一个StoreStore屏障(前面是写后面也是写),这就是内存屏障。

再让我们看表,如果第一个操作是普通写,第二个操作是volatile写,那么表格中对应的值就是NO,禁止重排序。这就是Volatile进行指令重排序的原理。

现在,我们只需要把上面代码的x和y用volatile修饰,就不会发生指令重排序了(如果你能通过表推一遍逻辑,你就能懂了)。

优秀的个人博客,低调大师

微信关注我们

原文链接:https://my.oschina.net/u/4873431/blog/4791243

转载内容版权归作者及来源网站所有!

低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。

相关文章

发表评论

资源下载

更多资源
优质分享Android(本站安卓app)

优质分享Android(本站安卓app)

近一个月的开发和优化,本站点的第一个app全新上线。该app采用极致压缩,本体才4.36MB。系统里面做了大量数据访问、缓存优化。方便用户在手机上查看文章。后续会推出HarmonyOS的适配版本。

Apache Tomcat7、8、9(Java Web服务器)

Apache Tomcat7、8、9(Java Web服务器)

Tomcat是Apache 软件基金会(Apache Software Foundation)的Jakarta 项目中的一个核心项目,由Apache、Sun 和其他一些公司及个人共同开发而成。因为Tomcat 技术先进、性能稳定,而且免费,因而深受Java 爱好者的喜爱并得到了部分软件开发商的认可,成为目前比较流行的Web 应用服务器。

Eclipse(集成开发环境)

Eclipse(集成开发环境)

Eclipse 是一个开放源代码的、基于Java的可扩展开发平台。就其本身而言,它只是一个框架和一组服务,用于通过插件组件构建开发环境。幸运的是,Eclipse 附带了一个标准的插件集,包括Java开发工具(Java Development Kit,JDK)。

Sublime Text 一个代码编辑器

Sublime Text 一个代码编辑器

Sublime Text具有漂亮的用户界面和强大的功能,例如代码缩略图,Python的插件,代码段等。还可自定义键绑定,菜单和工具栏。Sublime Text 的主要功能包括:拼写检查,书签,完整的 Python API , Goto 功能,即时项目切换,多选择,多窗口等等。Sublime Text 是一个跨平台的编辑器,同时支持Windows、Linux、Mac OS X等操作系统。