Java 内存模型

Java 内存模型简称 JMM,是 Java 中为了解决可见性和有序性问题而制定的一种编程规范和规则,与 JVM 实实在在的内存结构不同,JMM 只是一种编程规范和规则。

  1. 原子性指一个操作是不可中断的。即使是在多个线程一起执行的时候,一个操作一旦开始,就不会被其他线程干扰。
  2. 可见性指当一个线程修改了某一个共享变量的值,其他的线程是否能够立即知道这个修改。显然,对于串行程序来说,可见性问题是不存在的。因为我们在任何一个操作步骤中修改了某个变量,那么在后续的步骤中,读取这个变量一定是修改后的值。
  3. 有序性指对于一个线程的执行代码,我们习惯性的认为代码的执行是从前往后,依次执行的。但是在并发时,程序的执行可能会出现乱序。给人直观的感觉就是:写在前面的代码,可能会在后面执行。

Java 内存模型的概念

Java 内存模型规定了所有的变量都存储在主内存中,也就是存储在计算机的物理内存中,每个线程都有自己的工作内存,用于存贮线程私有的数据,线程对变量的所有操作都需要在工作内存中完成。一个线程不能直接访问其他线程工作内存的数据,只能通过主内存进行数据交互。如图,可以表示线程、主内存、工作内存的关系。
image.png

  1. 变量都存储在主内存中;
  2. 当线程需要操作变量时,需要先将主内存中的变量复制到对应的工作内存中。
  3. 线程直接读写对应的工作内存中的变量;
  4. 一个线程不能直接访问其他线程工作内存中的数据,只能通过主内存间接访问。

Java 内存模型的八大操作

对于线程工作内存与主内存之间的数据交互,JMM 定义了一套交互协议,规定了一个变量从主内存中复制到工作内存中,以及从工作内存同步到主存中的实现细节。JMM 同步数据的 8 种操作如表所示。

指令名称目标作用
lock锁定主存中的变量把主内存中的某个变量标记为线程独占
unlock解锁主存中的变量释放主内存中锁定状态的某个变量,释放后可以被其他线程再次锁定
store存储工作内存中的变量将工作内存中的某个变量传送到主内存中
write写入主内存中的变量将 Store 操作从工作内存中得到的变量值写入主内存的变量中
read读取主内存中的变量将主内存中的某个变量传送到工作内存中
load载入工作内存中的变量将 Read 操作从主内存中得到的变量值载入工作内存的变量中
use使用工作内存中的变量将工作内存中的某个变量值传递到执行引擎
assign赋值工作内存中的变量执行引擎将某个值赋值给工作内存中的某个变量

JMM 同步数据的具体流程如图所示:
image.png
JMM 还规定了这 8 种操作必须满足如下规则:

  1. 没有进行 assign 操作的线程允许将数据从工作内存种同步到主内存中;
  2. store 和 write 操作必须按顺序成对出现,但是可以不连续执行,它们之间可以插入其他指令;
  3. read 和 load 操作必须按顺序成对出现,但是可以不连续执行,它们之间可以插入其他指令;
  4. 如果一个线程进行了 assign 操作,则它必须使用 write 操作将数据写回主内存;
  5. 变量只能在主内存种生成,对变量执行 use 和 store 操作之前,必须先执行 assign 和 load 操作;
  6. 一个变量只允许同时被一个线程执行 lock 操作,可以被这个线程执行多次 lock 操作,但是后续需要执行相同次数的 unlock 操作才能解锁;
  7. 针对一个变量的 lock 和 unlock 操作必须成对出现;
  8. 对一个变量执行 lock 操作时,会清空工作内存中当前变量的值,当使用这个变量时,需要重新执行 load 或者 assign 操作加载并初始化变量的指;
  9. 不允许对一个没有执行 lock 操作的变量执行 unlock 操作,也不允许对其他线程执行了 lock 操作的变量执行 unlock 操作;
  10. 必须先对变量执行 store 和 write 操作将其同步到主内存中,才能对该变量执行 unlock 操作。

Java 内存模型解决可见性与有序性问题

为了解决可见性和有序性问题,Java 提供了按需禁用缓存和编译优化的方法,开发人员可以根据需要使用这些方法,如图所示
image.png
JMM 规范了 JVM 提供按需禁用缓存和编译优化的方法,如图所示
image.png
JMM 规划 JVM 禁用缓存和编译优化的方法包括 volatile、synchronized 锁和 final 关键字,以及 JMM 模型中的 Happens-Before 原则,如图所示:
image.png
JMM 同样提供了内存屏障来解决多线程之间的有序性问题,主要包括读屏障(Load Barrier)和写屏障(Store Barrier)两大类。

  1. 读屏障插入在读指令的前面,能够让 CPU 缓存中的数据失效,重新从主内存读取数据。
  2. 写屏障插入在写指令的后面,能够让 CPU 缓存的最新数据立马刷新到主内存。

参考:《深入理解高并发编程-核心原理与案例实战》

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

SuZhan7710

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值