多线程并发问题总结

多线程并发要保证安全需要满足可见性,有序性,原子性。在并发优化过程中会出现缓存一致,指令重排序等问题,在出现对应并发不安全问题时要如何处理?

首先看下并发优化演进过程:

并发演进过程

  • 多cpu之间有各自的缓存,在多线程环境,不同cpu之间的缓存会有缓存不一致的问题。这个问题不满足并发安全的可见性要求。操作系统可以通过总线锁和缓存一致性协议保证缓存一致性。

    • 总线锁,性能低。
    • 缓存一致性协议有MESI、MOSI协议等,MESI表示缓存行的4中状态。
      • M:modify,修改状态。该缓存行数据有效,数据修改了,和内存中的数据不一致,数据只存在本缓存行中
      • E:Exculsive,独享。该缓存行数据有效,和内存数据一直,只存在本缓存行中。
      • S:Shared,共享。该缓存行数据有效,和内存数据一直,数据同时存在其他缓存行中。
      • I:Invalid,失效。该缓存行数据失效。
      • MESI协议模拟地址:https://www.scss.tcd.ie/Jeremy.Jones/vivio/caches/MESI.html
  • 上述缓存一致性协议中cpu0和cpu1之间保证一致性,但会有同步问题。也就是说cpu0更新缓存行a=1,会通知cpu1把缓存行Cache的a失效掉,在cpu1返回失效确认前,不会执行cpu0对应线程后面的逻辑,返回失效确认后,才会去执行cpu0对应线程的后面的逻辑。为了提高性能,引入了Stroe Buffer和Invalid Queue(失效队列)来提高性能。

    • 增加Store Buffer和Invalid Queue后结构图如下:
      StoreBuffer
    • 当cpu0设置a=1时,会把a=1先存入在Store Buffer,通知cpu1失效期缓存行的a,cpu0不需要等待cpu1返回失效确认,会继续执行后面的逻辑;cpu1失效后,会把失效信息放在Invalid Queue,也是异步通知cpu0。这种情况会重现指令重排序问题。如下面代码,cpu0设置a=1,在没有等到cpu1的失效确认前,是不会更新缓存行Cache的,所以缓存行的内容还是a=0,cpu0继续执行后面的a=a+1,得到的结果时a=1,所以断言a==2失败,等待cpu1失效确认后,缓存行的a才会设置为1,从现象来看执行顺序是a=a+1在前,a=1在后,指令重排了。
    int a=0;
    private void func() {
      a=1;
      a=a+1;
      assert(a==2);
    }
    
    • 有序性问题还有可能是由于高级语言到计算机执行指令过程的优化导致的,如编译器优化>虚拟机优化>指令优化。
  • 指令重排序问题可以通过内存屏障解决。细分有读屏障,写屏障,全屏障。由于不同操作系统对存储屏障的命令不同,JVM抽象出了JMM模型(java内存模型)。我们可以通过volatile关键字修饰变量保证变量的可见性和有序性,但不能保证原子性,原子性需要通过加锁保证。final,Synchronize描述也可以保证有序性。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值