Java内存模型JMM(全称Java Memory Model)
1、JMM数据原子操作
- read(读取):从主内存读取数据
- load(载入):将主内存读取到的数据写入工作内存
- use(使用):从工作内存读取数据来计算
- assign(赋值):将计算好的值重新赋值到工作内存中
- strore(存储):将工作内存数据写入主内存
- write(写入):将store过去的变量值赋值给主内存中的变量
- lock(锁定):将主内存变量枷锁,表示为线程独占状态
- unlock(解锁):将主内存变量解锁,解锁后其他线程可以锁定该变量
过程如下
2、JMM缓存不一致问题
- 总线加锁(性能太低)
CPU从主内存读取数据到高速缓存,会在总线对这个数据加锁,这样其他CPU没法去读或写这个数据,直到这个CPU使用完整数据释放锁之后其他CPU才能读取该数据。
- MESI缓存一致性协议
多个CPU从主内存读取同一个数据到各自的高速缓存,当其中某个CPU修改了缓存里的数据,该数据会马上同步回主内存,其他CPU通过总线嗅探机制可以感知到数据的变化从而将自己缓存里的数据失效。
3、volatile为什么不能保证原子性
- 对任意单个volatile变量的读/写具有原子性,但类似于volatile++这种复合操作不具有原子性。
1)分析1
线程A加载num之后,use,进行num++,之后assign,num=1,在num store总线加锁之前,线程B执行,num的assign,num=1;这个时候,线程A进行lock,根据总线嗅探机制,这个时候线程B发现数据有变化,进行变量失效,导致,线程B执行的mum++的结果值失效,进而形成程序执行的效果;
2)分析2
以volatile int i=10;i++;为例分析:i++实际为load、increment、store三个操作
某一时刻线程1将i的值load取出来,放置到cpu缓存中,然后再将此值放置到寄存器A中,然后A中的值自增1(寄存器A中保存的是中间值,没有直接修改i,因此其他线程并不会获取到这个自增1的值)。如果在此时线程2也执行同样的操作,获取值i==10,自增1变为11,然后马上刷入主内存。此时由于线程2修改了i的值,实时的线程1中的i==10的值缓存失效,重新从主内存中读取,变为11.接下来线程1恢复。将自增过后的A寄存器值11赋值给cpu缓存i。这样就出现了线程安全问题。