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的赋值没有进行。
  • 4
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值