自学内容网 自学内容网

JUC-Java内存模型(JMM)与重排序

Java内存模型

线程之间的通信机制有两种:共享内存和消息传递。而java的并发是借助共享内存来完成线程之间的通信的。

JMM

Java的内存模型简称JMM,在Java中线程间的通信由JMM控制。JMM决定一个线程对共享变量的写入何时对另一个线程可见。即线程之间的共享变量存储在主内存中,每个线程都有一个私有的本地内存,本地内存中存储了该线程以读/写共享变量的副本,因此线程操作资源是修改的副本,而不是直接去主内存修改。JMM通过控制主内存与每个线程的本地内存之间的交互,来为Java程序员提供内存可见性保证。这就可能造成一个线程在主存中修改了一个变量的值,而另外一个线程还继续使用它在寄存器中的变量值的拷贝,造成数据的不一致
![[Pasted image 20240724101922.png]]

重排序

重排序是指编译器和处理器为了优化程序性能而对指令序列进行重新排序的一种手段

重排序的种类

  1. 编译器重排序
    • 编译器优化的重排序。编译器在不改变单线程程序语义的前提下,可以重新安排语句的执行顺序。
  2. 处理器重排序(JMM通过内存屏障指令来禁止特定类型的处理器重排序)
    • 指令级并行的重排序。在同一时钟周期内,同时运行多条指令。如果指令不存在依赖性,处理器有可能改变指令的执行顺序。
    • 内存系统的重排序。由于处理器使用缓存和读/写缓存区这使得加载和存储操作看上去可能是在乱序执行。

从Java源代码到最终实际执行的指令序列,会分别经历下面3种重排:

源代码 -> 编译器重排序 -> 指令级并行的重排序 -> 内存系统的重排序 -> 最终的执行指令序列

数据依赖性

如果两个操作访问同一个变量,且这两个操作中有一个为写操作,此时这两个操作之间就存在数据依赖性。存在数据依赖性的指令不会被编译器和处理器重排序。仅针对单个处理器中执行的指令序列和单个线程中执行的操作,不同处理器之间和不同线程之间的数据依赖性不被编译器和处理器考虑。
![[Pasted image 20240724155715.png]]

编译器、runtime、处理器都必须遵守as-if-serial语义:不管怎么重排序,单线程程序的执行结果不能被改变。

指令重排序实例

  • example 1:
public static void main(String[] args) {
    int a = 10; // 1
    int b = 11; // 2
    int c = a + b; // 3
}

123或213:a和c、b和c之间存在循环依赖,不会发生指令重排序。而a和b之间可能发生指令重排。

  • example 2:
public class Test3 {
    static int a, b, x, y;
    public static void main(String[] args) throws InterruptedException {
        new Thread(() -> {
            a = 1;
            x = b;
        }, "t1").start();

        new Thread(() -> {
            b = 2;
            y = a;
        }, "t2").start();
    }
}

以上代码有可能会出现x==y==0的情况:

  • a和b的赋值还没刷到主内存,x和y就直接从主内存中拿去a和b。
  • x=b和y=a先执行,而a和b的赋值没有进行。

原文地址:https://blog.csdn.net/m0_62740234/article/details/140671089

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!