JMM(Java Memory Model)内存模型详解
java线程内存模型
跟CPU缓存模型类似,是基于cpu缓存模型建立的,Java线程内存模型是标准化的,屏蔽掉了底层不同计算机的区别.
CPU缓存模型
JMM
证明工作内存的存在
public class JMM {
public static boolean initFlag = false;
public static void main(String[] args) {
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("thread1 开始.....");
while(!initFlag){
}
System.out.println("initFlag == true");
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("thread2开始...");
change();
}
}).start();
}
private static void change() {
initFlag = true;
System.out.println("已将initFlag改为true.....");
}
}
我们本来想的是,第二个线程把initFlag
改为true
后,第一个线程跳出循环,并打印initFlag == true
;我们看看运行结果
我们惊喜的发现,似乎线程1中的initFlag并没有该成true,还在死循环中,由此可见,线程2更改的值并没有对线程1造成影响,由此可以证明,工作内存的存在,各个工作内存是相互独立的
但是,我们想要的结果是跳出循环,那么我们就可以对共享变量加
volatile
关键字
public static volatile boolean initFlag = false;
JMM数据原子操作
- read (读取) : 从主内存读取数据
- load (载入) : 将主内存读取到的数据写入工作内存
- use (使用) : 从工作内存读取数据来计算
- assign (赋值) : 将计算好的值重新赋值到工作内存中
- store (存储) : 将工作内存的数据写入主内存
- write (写入) : 将store过去的变量值赋予给主内存中的变量
- lock (锁定) : 将主内存变量加锁,标识为线程独占状态
- unlock(解锁) : 将主内存变量解锁,解锁后的其他线程可以锁定该变量
(无volatile过程)
先看前四个原子操作的流程(无volatile)
目前到assign (赋值)操作之后,工作内存中的值更改
store和write
加上volatile后
但这样会出现缓存不一致现象,怎样解决
-
总线枷锁(性能太低)
- cpu从主内存读取数据到高速缓存,会在总线对这个数据加锁,这样其他cpu无法去读写这个数据,直到这个cpu使用完数据释放锁之后,其他cpu才能读取该数据
- cpu从主内存读取数据到高速缓存,会在总线对这个数据加锁,这样其他cpu无法去读写这个数据,直到这个cpu使用完数据释放锁之后,其他cpu才能读取该数据
-
MESI缓存一致性协议
- 多个cpu从主内存读取同一个数据到各自高速缓存,其中某个cpu修改了缓存里的数据,该数据会马上同步到主内存,其他cpu通过
总线嗅探机制
可以感知到数据的变化从而将自己缓存里的数据失败
- 多个cpu从主内存读取同一个数据到各自高速缓存,其中某个cpu修改了缓存里的数据,该数据会马上同步到主内存,其他cpu通过
-
假如现在有CPU2和CPU1,主内存有变量
initFlag = false
。现在cpu2要做initFlag = true
的操作。 -
如果在变量
initFlag = false
上加上volatile,则就会触发MESI -
当CPU1从主内存中读取到
initFlag = false
时,CPU1会把此变量标记成独享状态 -
并监听总线,是否有其它CPU去读取此变量
-
当CPU2从主内存中读取
initFlag = false
变量时,CPU1会通过嗅探机制监听到。 -
此时CPU2的
initFlag
变量会变成共享状态。继续进行赋值,赋值完变成initFlag = true
。 -
此时要回写到主内存之前。先锁住缓存行。并标记
initFlag
变量为修改状态。并向总线发消息。 -
CPU1监听总线时,会监听到,并把
initFlag
标记成无效状态。 -
CPU2把变量
initFlag = true
回写到主内存后,会由修改状态变成独享状态。 -
此时,如果CPU1如果想修改X变量时,要重启从主内存中读取。然后开始新的轮回。