是一种屏障指令,它使得CPU或编译器对屏障指令的前和后所发出的内存操作执行一个排序的约束。也叫内存栅栏或栅栏指令,volatile实现了Java内存模型中的可见性和有序性,但volatile无法保证原子性。
内存屏障之前的所有写操作都要回写到主内存,
内存屏障之后的所有读操作都能获得内存屏障之前的所有写操作的最新结果(实现了可见性)。
因此重排序时,不允许把内存屏障之后的指令重排序到内存屏障之前。
对一个 volatile 域的写, happens-before 于任意后续对这个 volatile 域的读,也叫写后读。
作用
- 阻止屏障两边的指令重排序
- 写数据时加入屏障,强制将线程私有工作内存的数据刷回主物理内存
- 读数据时加入屏障,线程私有工作内存的数据失效,重新到主物理内存中获取最新数据
happens-before 之 volatile 变量规则
第一个操作 | 普通读写 | volatile读 | volatile写 |
普通读写 | 可以重排 | 可以重排 | 不可以重排 |
volatile读 | 不可以重排 | 不可以重排 | 不可以重排 |
volatile2写 | 可以重排 | 不可以重排 | 不可以重排 |
当第一个操作为volatile读时,不论第二个操作是什么,都不能重排序。这个操作保证了volatile读之后的操作不会被重排到volatile读之前。
当第二个操作为volatile写时,不论第一个操作是什么,都不能重排序。这个操作保证了volatile写之前的操作不会被重排到volatile写之后。
当第一个操作为volatile写时,第二个操作为volatile读时,不能重排。
四大内存屏障
屏障类型 | 指令示例 | 说明 |
LoadLoad | Load1;LoadLoad;Load2 | 保证load1的读取操作在load2及后续读取操作之前执行 |
StoreStore | Store1;StoreStore;Store2 | 在store2及其后的写操作执行前,保证store1的写操作已刷新到主内存 |
LoadStore | Load1;LoadStore;Store2 | 在stroe2及其后的写操作执行前,保证load1的读操作已读取结束 |
StoreLoad | Store1;StoreLoad;Load2 | 保证store1的写操作已刷新到主内存之后,Ioad2及其后的读操作才能执行 |
写
1. 在每个 volatile 写操作的前⾯插⼊⼀个 StoreStore 屏障
StoreStore Barriers Storel;StoreStore;Store2
禁止重排序:一定是Storel的数据写出到主内存完成后,才能让Store.2
及其之后的写出操作的数据,被其它线程看到。
保证Stor1指令写出去的数据,会强制被刷新回到主内存中
2. 在每个 volatile 写操作的后⾯插⼊⼀个 StoreLoad 屏障
StoreLoad Barriers Storel:StoreLoad;Load2
禁止重排序:一定是Storel的数据写出到主内存完成后,才能让Load2来读取数据同时保证:强制把写缓冲区的数据刷回到主内存中让工作内存/CPU高速缓存当中缓存的数据失效,重新到主内存中获取新的数据
读
- 在每个 volatile 读操作的后⾯插⼊⼀个 LoadLoad 屏障
LoadLoad Barriers:Load1;LoadLoad;Load2
禁止重排序:访问Load2的读取操作一定不会重排到Load1之前保证Load2在读取的时候,自己缓存内到相应数据失效,Load2会去主内存中获取最新的数据
2. 在每个 volatile 读操作的后⾯插⼊⼀个 LoadStore 屏障
LoadStore Barriers:Loadl;LoadStore;Store2
禁止重排序:一定是Load1读取数据完成后,才能让Store2及其之后的写出操作的数据,被其它线程看到。
对比锁理解
- LOCk指令,相当于内存屏障,功能也类似内存屏障的功能
- 首先对总线/缓存加锁,然后去执行后面的指令,最后,释放锁,同时把高速缓存的数据刷新回到主内存
- 在lock锁住总线/缓存的时候,其它cpu的读写请求就会被阻塞,直到锁释放。Lock过后的写操作,会让其它cpu的高速缓存中相应的数据失效,这样后续在读取数据的时候,就会从主内存去加载最新的数据加了Lock指令过后的具体表现,就跟JMM添加内存屏障后一样。
总结
- volatile写之前的操作,都禁止重排序到volatile之后
- volatile读之后的操作,都禁止重排序到volatile之前
- volatile写之后volatile读,禁止重排序的