cpu的硬件架构:cpu读内存数据时,相当于将内存地址中的数据读入cpu,RAM速度慢,CPU速度快,为了高效传输数据,通常选择加入缓存
java内存模型和CPU缓存模型类似,是基于CPU缓存模型来建立的,java内存模型是标准化的,屏蔽掉了底层不同计算机的区别。
举个例子:
package com.ztb.entity;
public class VolatileTest {
private static boolean initFlag = false;//定义一个全局共享变量
public static void main(String[] args) throws InterruptedException {
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("waiting data...");
while (!initFlag) {//一直是true。是个死循环
}
System.out.println("第一个线程========data success");
}
}).start();
Thread.sleep(2000);//线程休眠两秒
new Thread(new Runnable() {//第二个线程执行任务
@Override
public void run() {
prepareData();
}
}).start();
}
public static void prepareData() {
System.out.println("第二个线程 prepare data");
initFlag = true;
System.out.println("第二个线程 prepare data end");
}
}
输出日志:
说明了两个线程间读取共享变量存在问题,第二个线程执行完成后initFlag已经置为true;而第一个线程无法获知initFlag已经·改变。
在共享变量中加上volatile关键字,保持多线程之间共享变量在多个线程之间工作内存的可见性,则线程1中的initFlag变量同步。
那么volatile 是怎么实现共享变量在多个线程之间工作内存的可见性的呢?
首先来看一下JMM的8种数据原子操作:
- read 读取,作用于主内存把变量从主内存中读取到本本地内存。
- load 加载,把从主内存中读取的变量加载到本地内存的变量副本中
- use 使用,把工作内存中的一个变量值传递给执行引擎,每当虚拟机遇到一个需要使用变量的值的字节码指令时将会执行这个操作。、
- assign 赋值 它把一个从执行引擎接收到的值赋值给工作内存的变量,每当虚拟机遇到一个给变量赋值的字节码指令时执行这个操作。
- store 存储 ,把工作内存中的一个变量的值传送到主内存中,以便随后的write的操作。
- write 写入 ,它把store操作从工作内存中一个变量的值传送到主内存的变量中。
- lock 锁定 :作用于主内存的变量,把一个变量标识为一条线程独占状态。
- unlock 解锁:作用于主内存变量,把一个处于锁定状态的变量释放出来,释放后的变量才可以被其他线程锁定。
画出程序执行的简易流程图:
线程一执行了 read load use操作之后 ,对initFlag取反,线程1进入while死循环
接下来线程2 执行read load use操作之后,对initFlag执行了assign 赋值操作,此时线程2工作内存中initFlag值变成true。 再执行store操作,将工作内存中的数据 写入主内存,执行write操作,将工作内存中一个变量的值传送到主内存的变量中,initFlag值置为true。
到此为止,线程一中的工作内存中initFlag中的值一直为false,cup无法再拿到主内存中变化的initFlag值。
造成jmm缓存不一致的问题解决办法
- 总线加锁(性能太低):
cpu从主内存读取数据到高速缓存,会在总线对这个数据加锁,这样其它cpu没法去读或写这个数据,直到这个cpu使用完数据释放锁之后其它cpu才能读取该数据
导致结果:改并行为串行。
2. MESI缓存一致性协议:多个cpu从主内存读取同一个数据到各自的高速缓存,当其中某个cpu修改了缓存里的数据,该数据会马上同步回主内存,其它cpu通过总线嗅探机制可以感知到数据的变化从而将自己缓存里的数据失效。
Volatile缓存可见性
实现原理底层实现主要是通过汇编lock前缀指令,它会锁定这块内存区域的缓存(缓存行锁定)并回写到主内存:
1)会将当前处理器缓存行的数据 即写回到系统内存。
2)这个写回内存的操作会引起在其他CPU里缓存了该内存地址的数据无效(MESI协议)
总结:我们再来看当程序,当线程2将initFlag数据写入主内存中时,经过数据总线,线程1的CPU通过总线嗅探机制对总线的数据做监听,当察觉initFlag数据不一致时,会将线程1中工作内存中的变量数据失效,从而线程1会再一次从主内存:中读取数据。