连环炮:内存模型 -> 原子性、可见性、有序性 - > volatile+可见性 -> volatile+有序性(指令重排 + happens-before) -> voaltile+原子性 -> volatile底层的原理(内存屏障级别的原理)
volatile + 原子性:不能够保证原子性,虽然说有些极端特殊的情况下有保证原子性的效果,杠精,拿着一些极端场景下的例子,说volatile也可以原子性,oracle,64位的long的数字进行操作,volatile
保证原子性,synchronized,lock,加锁
volatile底层原理,如何实现保证可见性的呢?如何实现保证有序性的呢?
(1)lock指令:volatile保证可见性
对volatile修饰的变量,执行写操作的话,JVM会发送一条lock前缀指令给CPU,CPU在计算完之后会立即将这个值写回主内存,同时因为有MESI缓存一致性协议,所以各个CPU都会对总线进行嗅探,自己本地缓存中的数据是否被别人修改
如果发现别人修改了某个缓存的数据,那么CPU就会将自己本地缓存的数据过期掉,然后这个CPU上执行的线程在读取那个变量的时候,就会从主内存重新加载最新的数据了
lock前缀指令 + MESI缓存一致性协议
(2)内存屏障:volatile禁止指令重排序
volatille是如何保证有序性的?加了volatile的变量,可以保证前后的一些代码不会被指令重排,这个是如何做到的呢?指令重排是怎么回事,volatile就不会指令重排,简单介绍一下,内存屏障机制是非常非常复杂的,如果要讲解的很深入
Load1:
int localVar = this.variable
Load2:
int localVar = this.variable2
LoadLoad屏障:Load1;LoadLoad;Load2,确保Load1数据的装载先于Load2后所有装载指令,他的意思,Load1对应的代码和Load2对应的代码,是不能指令重排的
Store1:
this.variable = 1
StoreStore屏障
Store2:
this.variable2 = 2
StoreStore屏障:Store1;StoreStore;Store2,确保Store1的数据一定刷回主存,对其他cpu可见,先于Store2以及后续指令
LoadStore屏障:Load1;LoadStore;Store2,确保Load1指令的数据装载,先于Store2以及后续指令
StoreLoad屏障:Store1;StoreLoad;Load2,确保Store1指令的数据一定刷回主存,对其他cpu可见,先于Load2以及后续指令的数据装载
volatile的作用是什么呢?
volatile variable = 1
this.variable = 2 => store操作
int localVariable = this.variable => load操作
对于volatile修改变量的读写操作,都会加入内存屏障
每个volatile写操作前面,加StoreStore屏障,禁止上面的普通写和他重排;每个volatile写操作后面,加StoreLoad屏障,禁止跟下面的volatile读/写重排
每个volatile读操作后面,加LoadLoad屏障,禁止下面的普通读和voaltile读重排;每个volatile读操作后面,加LoadStore屏障,禁止下面的普通写和volatile读重排
并发这块,往深了讲,synchronized、volatile,底层都对应着一套复杂的cpu级别的硬件原理,大量的内存屏障的原理;lock API,concurrenthashmap,都是各种复杂的jdk级别的源码,技术深度是很深入的