JMM基础-计算机原理
计算 a+b
CPU读取一次内存 100ns,读取a,b花费200ns
CPU计算只用0.6ns
执行a+b绝大部分的时间是在等待读取内存
所以引入了高速缓存(一级、二级、三级)
多核CPU共享L3
L1速度最快 容量最小
为了提高运行速度 引入了cache
为了充分利用cache就提出了JMM
Java内存模型(JMM)
工作内存,主内存,两个抽象概念
工作内存:包括cpu内部的寄存器,高速缓存,还包括主内存(RAM)的一部分,很小一部分在主内存
主内存:也有小部分可能在寄存器和高速缓存
假如变量count进行累加操作
count在主内存里面
当任意线程要进行累加时,会把count放到线程独有的工作内存中去,每个操作count的线程中有count的副本
线程只允许对线程工作内存中的count进行操作,不允许直接操作主内存的变量
每个线程的工作内存是独享的
堆内存理解成主内存,栈内存理解成工作内存
JMM导致的并发安全问题
变量的可见性、原子性
count 初始值为 0
两个线程计算count = count + 1
结果应该是2 ,但结果可能是1
两个线程并不知道对方对线程进行了修改,看不到对方对count的修改,存在可见性问题
怎么解决可见性问题?
volatile
1.强迫从主内存中读取一次变量
2.每当把变量修改了后强迫马上刷新到主内存
volatile只能保证可见性
volatile不能解决我们的安全性问题,volatile只是强迫读写,问题在于计算过程不是一个原子操作
synchronized同时保证了可见性和原子性,synchronized的强度比volatile的强度大
volatile是JDK为我们提供的最最最轻量级的同步机制
可以把对volatile变量的单个读/写,看成是使用同一个锁对这些单个读/写操作做了同步
volatile变量自身具有下列特性:
可见性: 对一个volatile变量的读,总是能看到(任意线程)对这个volatile变量最后的写入。
原子性: 对任意单个volatile变量的读/写具有原子性,但类似于volatile++这种复合操作不具有原子性。
流水线和重排序:
cpu可以一次执行多条指令
do(){
int a = 5 ;
int b = 6 ;
int c = 7 ;
int d = a ;
if( b == 6){
d = b;
}
}
我们的印象中cpu是顺序执行的,引入流水线和重排序后cpu可能一次性把四条全部执行了,可能在if(b == 6)之前 就把里面的 d = b 做了,把先算的东西放到内存中开辟的重排序缓存中,如果真的出现了if(b == 6)就把重排序缓存的结果直接放里面,不一定是if之后再算
intel cpu 最大可支持十级流水线,同一时刻可执行10条指令
安卓ARM架构三级流水线,同一时刻可执行3条指令
重排序改变了我们写代码顺序的含义 ,单线程一定符合我们的要求 ,多线程可能造成混乱
volatile可抑制重排序
volatile的应用场景:
只有一个线程写,多个线程读的时候,volatile用起来没有问题,可强迫写线程刷新到主内存,不然不知道什么时候才刷新到主内存,强迫读可以看到最新的变量(可见性)
多个线程对一个变量写时不能保证原子性
写操作之间没有任何关联,volatile用起来没有问题
JDK的并发工具中很多用:volatile + CAS 来替换synchronized
volatile 可以理解成无锁化编程
volatile的实现原理
有volatile变量修饰的共享变量进行写操作的时候会使用CPU提供的Lock前缀指令
1.将当前处理器缓存行的数据写回到系统内存
2.这个写回内存的操作会使在其他CPU里缓存了该内存地址的数据无效