浅析Java内存模型(JMM)

2022-05-09 20:21:56 浏览数 (1)

背景

随着cpu由单核变成多核,又有了超线程。所以就会出现这样的问题,多核cpu在各自的缓存处理数据后,当同步数据到同一块主内存时,无法确定以谁的缓存数据为准。所以为了解决cpu缓存一致性的问题,特地制定了一些操作协议,例如MSI、MOSI、Firefly等。而在这些操作协议下,对特定的内存或高速缓存进行读写访问的过程,就是内存模型。不同架构(ARM/X86等)的物理机有不同的内存模型。

为了屏蔽不同架构的机器上的内存访问差异,让Java程序在各种平台下都能达到一致的内存访问效果,所以Java也制定了一套内存操作协议,即Java内存模型。JMM保证了多线程下变量的缓存一致性。

工作内存和主内存

JMM主要目标是定义程序中各个变量的访问规则,即在JVM中将变量存储到内存和从内存中取出变量这样的底层细节,来实现缓存一致性。JMM在定义上将内存分为主内存和工作内存,主内存对应Heap,属于线程公有,工作内存对应虚拟机栈,属于每个线程私有。

同时JMM对内存中的变量做了以下规定:

  1. 规定所有的变量都存储在主内存中,线程不能直接读写主内存中的变量
  2. 每个线程有自己的工作内存,对变量的读取、赋值等必须在工作内存中进行
  3. 线程之间值的传递都需要通过主内存来完成。工作内存无法互相访问
  4. 工作内存保存了线程用到的变量的主内存副本拷贝

原子操作

既然JMM规定了变量在主内存和工作内存中如何传输和操作,同时也提供了八个原子指令来具体实现细节。

对于上面的八个操作指令,JMM也指定了一些规则:

  1. read/load、store/write必须成对出现
  2. 不允许线程丢弃最近的assign操作,即工作内存中变量修改之后必须同步到主内存
  3. 不允许线程无原因地(没有assign操作)将变量工作内存同步到主内存
  4. 变量只能在主内存中诞生,并且必须在工作内存中初始化才能使用。即use、store之前必须经过load和assign
  5. 一个变量在同一时刻只能被一个线程lock。但一个线程可以lock多次。几次lock,只有对应次数的unlock变量才能解锁
  6. 一个变量被lock后,会清空所有工作内存中此变量的值。再次使用需要重新load、assign来初始化
  7. 一个变量未被lock,则不允许对它执行unlock,也不允许unlock其他线程lock的变量
  8. 一个变量unlock前,必须先把此变量同步到主内存中(store、write)

如何理解这八条规则和八条指令?

指令中的lock/unlock是让一个变量被一个线程独享,其他六个指令都是变量在工作内存中的赋值和传输操作. 规则中最核心的就是第六条。JMM的出现是为了当一个线程对变量修改时,其他线程停止修改,并能获取最新的值来进行操作,从而保证数据的一致性。而lock可以让一个变量只能被一个线程修改,而且让其他线程的工作内存此变量的值失效,想用此变量必须通过指令来获取新的值。

应用场景

synchronize底层是由monitorenter/monitorexit来实现的,就相当于lock/unlock。在synchronize的作用范围内,只能一个线程进行操作,其他线程工作内存中的变量会失效,当此线程修改完unlock的时候,其他线程会重新加载变量最新值来进行操作,从而保证数据的一致性。volatile关键字也通过内存屏障实现了lock/unlock的功能。

结语

今天看了大冰的《啊 2.0》第一二章,感慨良多。希望我们都可以在各自的地方努力成长。

0 人点赞