JMM 内存操作指令

​在上篇文章《内存屏障是个什么鬼》提到了内存访问的一些指令。这篇文章就来系统地介绍这些指令。

JMM

Java 内存模型类似于高速缓存与主存之间的一个关系。


一句话解释下什么是 JMM:

每个线程执行过程中操作的内存,我们称之为工作内存。线程在操作主存中共享变量时,会将变量 load 到工作内存,执行完操作后,还得 save 回主存中。

内存操作指令

了解了JMM后,看下面一段代码:

public class Test {
    int a;
    public int getA() {
        return a;
    }
    public void setA(int a) {
        this.a = a;
    }
}

线程在执行看似简单的的get/set方法时,在内存层面其实涉及到了相对复杂许多的内存操作指令。

JMM 定义了8个原子内存操作指令:

  1. lock:作用于主存,把一个变量地址标记为一个线程独享;
  2. unlock:  作用于主存,释放这个变量地址,其他线程方可共享;
  3. read:  作用于主存,将主内存的变量值传输到工作内存,供随后的 load 指令使用;
  4. load: 作用于工作内存, 将 read 传输过来的变量值赋值给本地(工作内存)变量副本;
  5. use: 作用于工作内存,把变量的值传给 cpu 计算单元来使用;
  6. assign: 作用于工作内存,把 cpu 计算单元计算出来的值给本地变量;
  7. store:作用于工作内存,把本地变量传输到主存,供随后的 write 指令使用;
  8. write: 作用于主存,把 store 传输过来的变量值,赋值给主存中的变量副本;

重点关注 read-load 和 store-write 指令, 这四个指令涉及到工作内存和主存间的交互操作,内存屏障就是使用了这些指令。
 

long 和 double 的非原子操作协定

JMM 允许虚拟机将没有被volatile修饰的64位数据的读写操作划分为两次32位的操作来进行,即允许不同的虚拟机来自行选择是否要保证64位数据类型的load、store、read和write这四个操作的原子性。

非原子访问操作,在并发环境下就有可能出现中间态的数据,既不是原值,也不是线程中的修改的值。

这个其实不必过于担心,事实上,这种非原子操作基本很少见,原因有三:

  1.  主流的64位虚拟机不会出现非原子操作,32位的x86 hotSpot 有这个风险。

  2. CPU 中集成了浮点运算器,用来专门处理浮点数据,因此,即使使用了32位的虚拟机也不会出现非原子访问操作风险。

  3. Hotspot 从 JDK9 开始  新增了参数 -XX:+AlwaysAtomicAccesses 来要求虚拟机对所有数据类型都进行原子性的访问。

最后如果明确知道变量存在多线程并发修改的情况,并且你不放心的话,就在前面加个 volatile 就是了。


原创不易,如果觉得有用,请随手 分享、在看~

 

posted on 2021-12-16 09:30  XuHe1  阅读(128)  评论(0编辑  收藏  举报