Java内存分级和指令重排序

一. 三级内存

超级线程

从内存往缓存读数据按照快。这个块叫做缓存行,一行数据64字节。

缓存一致性

二、重排序

CPU值指令乱序执行

有序性即程序执行的顺序按照代码的先后顺序执行。

重排序:CPU执行指令并非严格按钮代码的执行指令顺序执行。

重排序验证

/**
 * CPU指令重排序
 */
public class TestReorder {
    /**
     * 初始化成员
     */
    int x = 0, y = 0, a = 0, b = 0;

    public void testReorder() throws InterruptedException {
        int i = 0;
        for(;;) {
            // 开始循环计数
            i++;
            // 重新初始化变量值
            x = 0; y = 0; a = 0; b = 0;
            Thread threadOne = new Thread(new Runnable() {
                @Override
                public void run() {
                    a = 1;
                    x = b;
                }
            });
            Thread threadTwo = new Thread(new Runnable() {
                @Override
                public void run() {
                    b = 1;
                    y = a;
                }
            });
            threadOne.start();
            threadTwo.start();
            threadOne.join();
            threadTwo.join();
            if(x == 0 && y == 0) {
                /**
                 * 指令按顺序执行不能出现x和y同时为0的情况,除非出现重排重排序
                 * threadOne中,x=b先于a=1和b=1,y=a先于b=1执行
                 */
                System.out.println("第" + i + "次 x = 0 且 y = 0");
                break;
            }
        }
    }
}

Connected to the target VM, address: '127.0.0.1:55151', transport: 'socket'
第94772次 x = 0 且 y = 0

为什么进行重排序?

提高程序执行性能。在执行程序时为了提高性能,编译器和处理器常常会对指令做重排序。

重排序分为三类

  • 编译器优化的重排序

编译器在不改变单线程程序语义的前提下(代码中不包含synchronized关键字),可以重新安排语句的执行顺序。

  • 指令级并行的重排序

现代处理器采用了指令级并行技术(Instruction-Level Parallelism, ILP)来将多条指令重叠执行。如果不存在数据依赖性,处理器可以改变语句对应机器指令的执行顺序。

  • 内存系统的重排序。

由于处理器使用缓存和读/写缓冲区,这使得加载和存储操作看上去可能是在乱序执行。

Java从源代码到指令执行,需要经过

这里写图片描述

1)是编译器优化的重排序,2)和3)是处理器重排序。

可以通过内存屏障指令来禁止特定类型的处理器重排序。

三、volidate

作为指令 关键字,确保本条指令不会因 编译器的优化而省略,且要求每次直接读值.

volidate的主要作用有两个:

  • 保证可见性
  • 禁止指令重排序

可见性是指当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看得到修改的值。

原理:

JVM编译后,生成的汇编指令中,对于volatile标记的变量,会添加一个 Lock前缀
volatile实现的两条原则:

  • Lock前缀指令会引起处理器缓存回写到内存
  • 处理器将数据回写到内存会导致其他处理器的缓存无效

线程中处理器一直在总线上进行查看进行探测情况,一旦发现其他处理器要修改这个内存地址的值,那么,就让自己的高速缓存区变为无效状态,从内存进行读取.

测试程序

/**
 * 测试Volatile
 */
public class TestVolatile {
    /**
     * 是否需要暂停
     */
    volatile boolean isPause = false;

    /**
     * 测试可见性
     */
    public void test() throws InterruptedException {
        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("线程启动了");
                // 检测到暂停标记为true时停止子线程
                while (true) {
                    if (isPause) {
                        System.out.println("线程暂停处理 isPause:" + isPause);
                        break;
                    }
                }
            }
        }, "thread2");
        thread.start();
        Thread.sleep(1000);
        // 设置暂停标记
        isPause = true;
        System.out.println("设置isPause:" + isPause);
    }
}

Java内存模型规定了所有的变量都存储在主内存中,每条线程还有自己的工作内存,线程的工作内存中保存了该线程中是用到的变量的主内存副本拷贝,线程对变量的所有操作都必须在工作内存中进行,而不能直接读写主内存。不同的线程之间也无法直接访问对方工作内存中的变量,线程间变量的传递均需要自己的工作内存和主存之间进行数据同步进行。所以,就可能出现线程1改了某个变量的值,但是线程2不可见的情况。

Java内存分级和指令重排序_第1张图片

内存三级缓存接口。

Thread1创建后,将主内存isPause读取到线程的缓存副本,Thread2将主内存的isPause读取到自己的缓存副本。

1)Thread1修改isPause=true,因为是volatile,将isPause的值同步更新到主内存。

2)主内存中isPause=true,通知其他线程变量isPause更新,

3)Thread2将变量副本isPause‘设置为无效状态。

4)Thread2循环读取副本isPause‘,发现是无效状态,回退到主内存读取isPause,此时isPause=true,停止循环。

参考文献:

https://blog.csdn.net/yjp198713/article/details/78839698

你可能感兴趣的:(java,jvm)