首页 > Java > 关于 CPU 乱序执行的证明

关于 CPU 乱序执行的证明

在学习 volatile 关键字的时候,我们都知道他有两个作用:1. 内存可见性;2. 禁止指令重排序。但是我们一般都是说,那么怎么证明呢?请看下面这段代码:


package cn.bridgeli.demo;

/**
 * @author BridgeLi
 * @date 2020/7/4 10:27
 */
public class Disorder {

    private static int x = 0;
    private static int y = 0;
    private static volatile int a = 0;
    private static volatile int b = 0;

    public static void main(String[] args) throws InterruptedException {
        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;
                }
            }, "one");

            Thread two = new Thread(new Runnable() {
                @Override
                public void run() {
                    b = 1;
                    y = a;
                }
            }, "two");

            one.start();
            two.start();
            one.join();
            two.join();

            if (0 == x && 0 == y) {
                System.out.println("第 " + i + " 次(" + x + ", " + y + ")");
                break;
            }
        }
    }
}

如果仔细分析这段代码,我们就会发现,如果 CPU 没有乱序执行,那么无论任何时候 x 和 y 都不可能同时为零,但是事实上,这段代码是有可能出现 x 和 y 同时为零的,具体大家可以自己测试,需要说明的时候,什么时候指令重排了,要看运气,可能很快出现,也可能要等一会。

不过需要说明的是,实际上 CPU 的乱序执行,说的是指令级别的乱序执行,也就是:


Object o = new Object();

编译成字节码会有多条指令,例如实例化、初始化、o 指向申请的内存空间等等,而初始化和 o 指向申请的内存空间是可以乱序执行的,所以这也是 DCL 单例锁要加 volatile 关键字的原因。

最后简单说一下 volatile 是如何做的禁止指令重排

Java 内存模型其实是通过内存屏障(Memory Barrier)来实现的,Java 内存模型的重排规则会要求 Java 编译器在生成 JVM 指令时插入特定的内存屏障指令,通过这些内存屏障指令来禁止特定的指令重排序。在 hotspot 中的实现,在 bytecodeinterpreter.cpp 源码中有这么一段代码:


int field_offset = cache->f2_as_index();
    if (cache->is_volatile()) {
        if (support_IRIW_for_not_multiple_copy_atomic_cpu) {
            OrderAccess::fence();
        }

具体到 orderaccess_linux_x86.inline.hpp 源码中:


inline void OrderAccess::fence() {
  if (os::is_MP()) {
    // always use locked addl since mfence is sometimes expensive
#ifdef AMD64
    __asm__ volatile ("lock; addl $0,0(%%rsp)" : : : "cc", "memory");
#else
    __asm__ volatile ("lock; addl $0,0(%%esp)" : : : "cc", "memory");
#endif
  }
}

也就是通过 lock addl 指令锁北桥总线,实现禁止指令重排

参考资料:马士兵教育,多线程与高并发

全文完,如果本文对您有所帮助,请花 1 秒钟帮忙点击一下广告,谢谢。

作 者: BridgeLi,https://www.bridgeli.cn
原文链接:http://www.bridgeli.cn/archives/682
版权声明:非特殊声明均为本站原创作品,转载时请注明作者和原文链接。
分类: Java 标签: ,
  1. 本文目前尚无任何评论.

请输入正确的验证码