Java 内存模型

目录

    • Java 内存模型的概念
    • Java 内存模型的八大操作
    • Java 内存模型解决可见性与有序性问题

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

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

Java 内存模型的概念

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

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

Java 内存模型的八大操作

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

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

JMM 同步数据的具体流程如图所示:
Java 内存模型_第2张图片
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 提供了按需禁用缓存和编译优化的方法,开发人员可以根据需要使用这些方法,如图所示
Java 内存模型_第3张图片
JMM 规范了 JVM 提供按需禁用缓存和编译优化的方法,如图所示
Java 内存模型_第4张图片
JMM 规划 JVM 禁用缓存和编译优化的方法包括 volatile、synchronized 锁和 final 关键字,以及 JMM 模型中的 Happens-Before 原则,如图所示:
Java 内存模型_第5张图片
JMM 同样提供了内存屏障来解决多线程之间的有序性问题,主要包括读屏障(Load Barrier)和写屏障(Store Barrier)两大类。

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

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

你可能感兴趣的:(java,并发编程)