并发编程之可见性(上)

可见性问题

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]) 表示缓存已经失效
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值