Java 内存模型(Java Memory Model, JMM)的目标是解决可见性和有序性导致的并发安全 问题。通过屏蔽各种硬件和系统的内存访问差异,使 Java 在不同平台下能达到一致的内存 访问效果

物理内存模型

物理内存在并发问题下存在的一些问题以及解决方案

  1. 硬件处理效率 由于存储设备和 CPU 处理器的运算速率差距很大,需要使用高速缓存作为中间缓冲。先 将需要用到的数据复制到缓存中,CPU 从缓存中获取数据进行计算,然后将结果从缓存 同步到内存中

  2. 缓存一致性 在多处理器系统中,每个处理器都有自己的高速缓存,它们共享同一个主存,因此可能 导致缓存数据不一致问题。因此缓存和内存交换数据时要遵守一致性协议

  3. 代码乱序执行优化 为了让多个处理器的运算单元被充分利用,代码的执行顺序可能和输入顺序不一致,通 过优化会对运算结果进行重组,保证结果和顺序执行无异(但代码执行顺序可能被打乱)

JMM

主内存和工作内存

JMM 的主要目标是定义程序中各个变量的访问规则,它们是如何存储到内存的,又是如何被 访问的。这里的变量不包括局部变量和方法参数,因为它们是线程私有的,不存在并发安全 问题

JMM 规定所有变量都要存储在主内存中

每个线程会有自己的工作内存(本地内存),用来保存主内存中变量的副本,工作内存是一 个抽象概念,根据不同情况可能包括高速缓存、写缓冲区、寄存器等不同的物理位置

TODO 内存交互操作

并发安全特性

  • 原子性:即保证一个或多个操作在执行期间不会被打断,要么不执行要么全部执行。Java 通过 synchronized 关键字设置临界区,在进入和退出临界区时分别执行 monitorenter 和 monitorexit 两条 JVM 指令

  • 可见性:指当一个线程修改了一个共享变量时,其他线程可以立刻知晓。JMM 要求变量在 修改后要从工作内存同步到主内存,读取变量时要先从主内存同步变量值,以此实现可见 性

  • 有序性:在多个线程间执行非同步代码时,由于指令重排序优化,所有代码都可能交叉执 行。唯一起作用的约束:对于同步方法、同步块(synchronized)和 volatile 字段的操 作维持相对有序

    • volatile: 禁止这个字段指令重排序
    • synchronized: 通过互斥保证只有一条线程执行

TODO Happens-Before

内存屏障

内存屏障在 Java 中用来保证有序性和可见性,其本身就是一条 CPU 指令。在原有的指令 序列中插入内存屏障(指令),禁止这条指令之前的指令和之后的指令交换次序(同一边则 允许交换),保证程序的有序性;同时屏障指令会使处理器在写入、读取值之前,将内存的 值写入高速缓存,清空无效队列,实现缓存数据的一致性,从而保证可见性

对于 Inst1; <InstBarrier>; Inst2 , 常见的 4 种屏障指令作用如下:

  1. LoadLoad: 在 Inst2 及后续读取操作要读取的数据被访问前,保证 Inst1 要读取的数 据被读取完毕

  2. StoreStore: 在 Inst2 及后续写入操作执行前,保证 Inst1 的写入操作对其他处理器 可见

  3. LoadStore: 在 Inst2 及后续写入操作执行前,保证 Inst1 要读取的数据被读取完毕

  4. StoreLoad: 在 Inst2 及后续读取操作要读取的数据被访问前,保证 Inst1 的写入操作 对其他处理器可见。这条屏障指令是开销最大的,需要 flush 写缓冲区,清空无效队列