Java内存模型基础篇

我们在这里简单复习一下操作系统中的缓存

img

操作系统中的缓存情况为上图,操作系统将内存,缓存分为多个大小相等的块。然后根据缓存的数目依次指定内存块所对应的缓存块,在使用时,直接访问缓存,未命中则更新。

但是操作系统的缓存有一个很关键的隐含信息:每个内存块只有一个缓存,一个缓存对应了多个内存。

但是在Java内存模型中,情况却似乎反了过来,如下图

Java内存模型

image-20210127105906463

JMM

在Java中,

  1. 实例域、静态域和数组元素都存储在堆内存中,堆内存在线程之间共享(也就是共享变量
  2. 局部变量,方法定义参数,异常处理参数不会在线程之间共享(这些在语法上被限制了不能共享)

这就导致了线程可见性的问题,若两个线程希望通信:

在计算机的空间内,进程或者线程通信(只要两个线程有牵扯,都可以称为通信)只有两种方式

  • 把信息放在一个公开的场合,等目标去读(共享内存
  • 直接上门把信息送到(消息传递

但是对于JMM的模型,通信的实质还是通过共享变量的交互,

既然通过共享变量就注定要走这几个流程:

  1. 线程A,B读取共享变量到自己的缓存
  2. A做了操作,更新了缓存
  3. 缓存写回到主内存
  4. B更新缓存,得到信息

image-20210127112728160

指令重排序

在操作系统、计算机组成等等中都有提到指令重排序。

假设当前有这样一个任务:

  1. 读取变量1,
  2. 计算变量1*500,
  3. 读取变量2,
  4. 计算变量1+变量2

在程序中,为了最大程度利用计算机的资源,会对指令进行重排序,保证各个设备是一直在工作的,或短时间内完成工作。

可能的执行顺序:1324

指令重排序的理念从计算机的底层编译器都有应用到

重排序分3种:

  1. 编译器优化的重排序
  2. 指令级并行的重排序
  3. 内存系统的重排序

image-20210127120903969

但是在多线程的操作中,指令重排序意味着内存可见性的问题。

为了保证缓存一致性,内存可见性

  • JMM的编译器会禁用一些情况的重排序
  • JMM通过插入内存屏障来保证禁止特定类型的处理器重排序。

这里出现了一个新词语:内存屏障

什么是内存屏障?
内存屏障主要为读写屏障,是为了确保两步操作的先后读读、写写、写读、读写

image-20210127121445040

而指令重排序的原则来源于happens-before

与程序员密切相关的happens-before规则如下(很重要)。

  1. 程序顺序规则:一个线程中的每个操作,happens-before于该线程中的任意后续操作。
  2. 监视器锁规则:对一个锁的解锁,happens-before于随后对这个锁的加锁。
  3. volatile变量规则:对一个volatile域的写,happens-before于任意后续对这个volatile域的读。(对一个volatile变量的读,总是能看到(任意线程)对这个volatile变量最后的写入。即可见性)
  4. 传递性:如果A happens-before B,且B happens-before C,那么A happens-before C。

总结为四点:单线程不变,锁,volatile,传递

image-20210127121851591

一个happens-before规则对应于一个或多个编译器和处理器重排序规则。Java程序员只需要熟悉该原则,无需理解下层实现。

猜你喜欢

转载自blog.csdn.net/weixin_44494373/article/details/113244175
今日推荐