什么是缓存一致性
说到volatile必须了解的是缓存之间的一致性,
假设有双核cpu。在CPU1运行线程1,在CPU运行线程2,两个线程共享1个变量为TEMP。TEMP并未被volatile修饰,此时线程1对TEMP进行写操作,线程2对TEMP只有读取操作。无论线程1如何修改TEMP线程2都不会感知到。原因:线程1和线程2维护各自的cache而cache中维护各自的TEMP。线程1有写操作,在写入成功会把TEMP从cache刷新到主存中。而线程2不会从主从中拿数据,只会永远从cache里面拿数据,因为线程2只有读取操作(java优化机制)。
如何解决缓存不一致?
- 加总线锁 (太耗性能)
- 使用缓存一致性协议(java中可以加volatile)
java线程与缓存之间的关系
happends before原则
1.在同一个线程中,书写在前面的先于书写在后面的
2.unlock必须发生在lock之后
3.volatile修饰的变量,对一个变量的写操作先于读操作
4.传递规则:操作A先于B,B先于C那么A先于C
5.线程的启动规则:start方法先于线程执行操作
6.线程的中断规则:interrupt动作必须发生在捕获之前
7.对象销毁规则:对象的初始化必须发生在finalize之前
8.线程的终结规则:线程的所有操作必须发生在线程死亡之前
代码模拟缓存不一致现象
实现目标:
一个线程修改initSize,一个线程监听修改
如下代码所示:在reader线程和writer线程共享变量initSize。而期待当writer修改了initSize此时reder会读取到变化。但是reder基本没有读取到变化。在writer写入时确实将initSize刷新到主存了但是reader并未从主存拿取数据。因为在reder中对initSize只有读取操作,java对这种读取操作优化为:只在缓存中拿数据不会再去主存拿取数据所以导致initSize的值很少概念或基本不变。
如何解决?可以使用volatile关键字,它可以保证数据的可见性,writer只要修改了initSize。此时reder读取缓存中的initSize就会失效,必然去主存拿取数据。
public class VolatileDemo {
private static int initSize = 0;
private static final int MAX =5;
public static void main(String[] args) {
new Thread(()->{
int local = initSize;
while(local < 5){
if(local != initSize){
System.out.println("initSize 已经被更新为 "+initSize);
local = initSize;
}
}
},"Reader").start();
new Thread(()->{
int temp = initSize;
while(temp < MAX){
System.out.println("更新 initSize 为"+ (++temp));
initSize = temp;
try {
Thread.sleep(500L);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"Writer").start();
}
}
总结
volatile只能保证可见性和顺序性,因为共享资源的安全性问题无法保证原子性
使用场景
1.状态量标记
作为boolean类型的标记
2.保证线程屏障前后的一致性
voilatile修饰的变量作为屏障,屏障后面的肯定发生在前面的后面。但是屏障前面的代码间的具体顺序就无法保证了。