1. JMM
- 概述
JMM的全称是Java Memory Model(Java内存模型),线程间通讯是通过共享内存来实现的,所以也叫共享内存模型,它是多线程和并发编程的基础。
内存模型描述了程序中各个变量(实例域、静态域和数组元素)之间的关系,以及在实际计算机系统中将变量存储到内存和从内存中取出变量这样的底层细节
通过该内存模型屏蔽掉各种硬件和操作系统的内存访问差异,以实现让Java程序在各种平台下都能达到一致的内存访问效果,实现了跨平台
- 规范
JMM规定了所有的变量都存储在主内存中。每个线程还有自己的工作内存, 线程的工作内存中保存了该线程使用到的变量的主内存的副本拷贝,线程对变量的所有操作(读取、赋值等)都必须在工作内存中进行,而不能直接读写主内存中的变量。不同的线程之间也无法直接访问对方工作内存中的变量,线程之间值的传递都需要通过主内存来完成。
-
内存原子操作
结合以下代码进行分析一下内存的8个原子操作
public class JMMTest { private static boolean flag = false; public static void main(String[] args) { new Thread(()->{ while (!flag) { //wait } System.out.println("=======success====="); }, "threadB").start(); //保证线程B先执行 try { TimeUnit.MILLISECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } new Thread(()->{ flag = true; }, "threadA").start(); } }
大致过程如下图所示
对于线程B
1)在程序使用flag变量之前先要进行read
操作,将主内存的共享变量flag传输到线程的工作内存
2)将传输到工作内存的变量进行load
操作,此时线程B中就flag=false
3)CPU使用工作内存的变量进行use
操作,对flag进行取反运算对于线程A
1)在程序使用flag变量之前先要进行read
操作,将主内存的共享变量flag传输到线程的工作内存
2)将传输到工作内存的变量进行load
操作,此时线程A工作内存中就flag=false
3)CPU使用工作内存的变量进行use
操作,将flag的值设置为true
4)将CPU运算的结果赋值给工作内存的变量进行assign
操作,此时线程A工作内存中flag为true下面我们对代码稍作修改,将flag的定义加入
volatile
进行修饰private static volatile boolean flag = false;
因为flag用了volatile修饰,它将给flag带来以下两个特性
1)发生assign操作后将工作内存的值立即写回主内存
2)开起总线的嗅探机制,写回主内存时将其他工作内存中的该变量置位失效(MESI缓存一致性)此时接着上面介绍内存原子操作继续讲,对于线程A
5)立即将flag的值写回主内存,此时将先会执行lock
操作,将该内存行进行锁定,以防其他线程进行脏读
6)然后执行store
操作,将flag的值传输回主内存
7)主内存中执行write
操作,将主内存的flag值置位true
8)最后将锁释放,执行unlock
操作对于线程B,由于总线嗅探机制发现flag的值发生改变,线程B工作内存中的flag变量将失效,使用时需要重新从主内存读取,执行read和load操作。在执行read操作时如果发现该内存地址被锁住,则需要等到锁的释放