Java线程内存模型
1.多核并发缓存架构
中央处理器(CPU,central processing unit)
早期的计算机的cpu在计算时会直接与主内存打交道,因为主内存的效率太慢,所以在cpu与主内存之间加了cpu高速缓存。
2.JMM内存模型(JavaMemoryMode)
用下面的代码来证明每个线程都有自己的工作内存,如下图代码的运行结果:
运行效果如下:
为了使得线程都能够共享initFlag变量的值,我们在定义initFlag变量时加上volatile关键字,如下图:
运行结果如下:
问:volatile的作用?
答:内存可见性(Memory Visibility):所有线程都能看到共享内存的最新状态。
3.JMM数据原子操作
结合上述代码,volatile的底层机制原理分析如下:
线程1先从主内存中读取initFlag=false的值,进入死循环。线程2读取initFlag=false并修改为true并同步回主内存,但是该变量没有加volatile关键字,所以线程2修改了initFlag=true但是线程1没办法感知到,因此线程1会一直进入死循环。
问:如何解决JMM缓存不一致的问题?
答:方式一:早期采用的是总线加锁机制,当有一个cpu过来读取主内存的值时,总线会对该变量进行lock,此时则所有的cpu就会处于等待机制,直到那个cpu修改完变量值并同步给主内存后,总线才会unlock(释放锁),因此性能太低。因此采用MESI缓存一致性协议,会在read之前先lock。
方式二:加上volatile关键字后,其他的cpu就会开启MESI缓存一致性协议,开启总线嗅探机制来监听总线,因为线程2修改了initFlag的值在store的过程中会经过总线,那么线程1监听到initFlag的值已经修改,就会将自己工作内存中的initFlag值的状态置为失效,此时就会重新去主内存读取initFlag的值。volatile会在该cpu同步回主内存之前进行lock操作,直到同步完成后再unlock。如下图:
4.volatile底层原理分析
加了volatile关键字后,将代码使用汇编语言编译后,即使用的是lock的前缀指令,如上图,(1)会立即写回系统内存(主内存)的时候,会触发其他cpu的MESI协议和嗅探机制,则其他CPU就会将自己工作内存中的initFlag值的状态置为失效,此时就会重新去主内存读取initFlag的值。
5.并发编程volatile的可见性、原子性和有序性
synchronized可以保证原子性。如下图:volatile没办法保证原子性,工作内存中数据会失效。