可见性问题
Demo
public class VolatileDemo {
public static boolean stop=false;//方法1是给这个变量加上Volatile关键字
public static void main(String[] args) throws InterruptedException {
Thread t1=new Thread(()->{
int i=0;
while(!stop){
i++;
// 方法2解决 try {
// Thread.sleep(0);
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
// 原因是Thread.sleep会让CPU切换状态,然后会重新去读取内存中的值,即重新加载
// 方法3 System.out.println(i); 原因是I/O操作
}
});
t1.start();
System.out.println("begin start thread");
Thread.sleep(1000);
stop=true;
}
}
这个Demo跑起来不会停止,说明主线程暂停后,修改stop的值后,t1这个线程看不到修改结果就一直在循环。
如果把stop静态成员变量加上Volatile关键字就可以解决这个问题了
说明Volatile可以让一个线程的修改结果让其它线程可见
为什么会产生可见性问题?
CPU、内存以及I/O设备不断迭代升级来提升计算机处理性能时,有一个非常核心的矛盾点,就是这三者在处理速度的差异。CPU的计算速度是非常快的,其次是内存、最后是IO设备(比如磁盘),也就是CPU的计算速度远远高于内存以及磁盘设备的I/O速度。
如图所示
- 计算机是利用CPU进行数据运算的,但是CPU只能对内存中的数据进行运算,对于磁盘中的数据,必须要先读取到内存,CPU才能进行运算,也就是CPU和内存之间无法避免的出现了IO操作。
- cpu的运算速度远远高于内存的IO速度,这就导致了一旦和内存I/O操作后,CPU就阻塞了(这个时候不讨论让CPU阻塞去做其它事,那是多线程的问题)
- 可以总结到:CPU从单核升级到多核甚至到超线程技术在为了提高CPU的处理性能,但是仅仅提升CPU性能是不够的,如果内存和磁盘的处理性能没有跟上,就意味着整体的计算效率取决于最慢的设备,
为了平衡这三者之间的速度差异,最大化的利用CPU,在硬件层面、操作系统层面、编译器层面做出了很多的优化
- CPU增加了高速缓存(直接减少和内存I/O操作所消耗的时间)
- 操作系统增加了进程、线程。通过CPU的时间片切换最大化的提升CPU的使用率(多线程技术)
- 编译器的指令优化,更合理的去利用好CPU的高速缓存(高速缓存空间有限嘛,所以就需要尽量让访问频率高的放在缓存中)
而每种优化都会带来相应的问题,而这些问题是导致线程安全性问题的根源!
CPU层面的缓存
为了减少CPU和内存之间IO操作所消耗的时间,在CPU层面设计了高速缓存。
- 这个缓存行可以缓存存储在内存中的数据,CPU每次会先从缓存行中读取需要运算的数据
- 如果缓存行中不存在该数据,才会从内存中加载
- 通过这样一个机制可以减少CPU和内存的交互开销从而提升CPU的利用率。
对于主流的x86平台,cpu的缓存行(cache)分为L1、L2、L3总共3级。(缓存行时CPU和内存交互的最小单元)
L1最快,L2其次,L3最差,离CPU越近速度越快
缓存一致性问题
不管什么缓存都存在缓存一致性问题,需要解决!
如图所示,两个线程都拿到了flag的值并保存在高速缓存中,左线程修改flag值为true时会把它写入内存中,而这个时候右线程不会知道flag的值已经改变了(如果我们不自己去解决的话),还是会继续去拿它自己的高速缓存中的值。
但其实现在flag在内存和高速缓存中的值已经不一样了,这就是缓存一致性问题!
缓存锁和总线锁
由缓存一致性问题我们可以想到加锁来解决。锁又分为缓存锁和总线锁参考文章
缓存一致性协议
如果是采用缓存锁,那么为了达到数据访问的一致,需要各个处理器在访问缓存时遵循一些协议,在读写时根据协议来操作,常见的协议有MSI,MESI,MOSI等。最常见的就是MESI协议。接下来给大家简单讲解一下MESI。
MESI表示缓存行的四种状态
- M(Modify[ˈmɒdɪfaɪ]) 表示共享数据只缓存在当前CPU缓存中,并且是被修改状态,也就是缓存的
数据和主内存中的数据不一致 - E(Exclusive[ɪkˈskluːsɪv]) 表示缓存的独占状态,数据只缓存在当前CPU缓存中,并且没有被修改
- S(Shared[ʃerd]) 表示数据可能被多个CPU缓存,并且各个缓存中的数据和主内存数据一致
- I(Invalid[ˈɪnvəlɪd]) 表示缓存已经失效