并发编程(三)线程安全性

  1. 共享资源是否有多个线程同时访问
  2. 希望结果跟预期的一致

Volatile

作用:保证共享资源的可见性

如何保证可见性(hsdis工具)?

通过反编译可以看到多了一个汇编Lock指令,相当于下面说的内存屏障的功能

什么是可见性?

硬件层面
image-20210227103950199

cpu的高速缓存:分为L1(指令缓存)、L2(数据缓存)、L3 -->性能逐步下降

为了最大化利用cpu资源:优化了如下模块

  1. cpu增加高速缓存
  2. 引入线程、进程
  3. 指令优化:重排序

硬件层面解决可见性

  1. 总线锁:处理器之间的操作互斥->性能差

cpu获取总线锁,总线发出lock信号,其他处理器不能操作主内存

  1. 缓存锁:降低锁力度

如何实现缓存锁?通过MESI协议

image-20201122225252551 image-20201122225310337

cpu缓存一致性协议(MESI):缓存的4种状态

  1. M (Modify) 共享数据缓存在当前cpu中,并且是修改状态,即缓存数据和主内存不一致
  2. E (Exclusive) 数据缓存在当前cpu中,独占并且没有被修改
  3. S (Shared) 数据被多个cpu缓存,并且与主存一致
  4. I (Invalid) 表示缓存已经失效

读请求:M、E、S状态的缓存可以被读取,I状态只能从主存中读取

写请求:M、E状态可以写,S状态需要使其他CPU缓存失效

**引入缓存锁后仍存在的问题:**处于S状态的cpu进行写请求的时候需要跟其他CPU通信,这就会存在阻塞问题

因此引入storebuffer缓冲区(相当于异步处理),在cpu当中,主内存读取先读buffer,但是仍有重排序等问题

image-20210226171817515

在没有屏障指令的情况下,两个cpu同时执行,cpu0对于value=10先写入storebuffer在通知cpu1,继续执行isFinish,因为e状态可以直接修改修改。cpu1读取到isFinish为true,但此时的value还未修改为10,因此assert判断为false

CPU层面提供了指令->内存屏障

内存屏障:用来解决可见性问题

CPU层面提供了3种屏障

  1. 写屏障:写之前的缓存同步到主存
  2. 读屏障:基于失效队列
  3. 全屏障
JMM的内存模型层面

导致可见性的根本原因:高速缓存和重排序

JMM的核心是解决了有序性和可见性

可见性和有序性解决方式:通过volatile、synchronized、final、happens-before

重排序的类型:

  1. 编译重排序
  2. CPU层面的重排序:指令重排序、内存重排序

不会进行重排序的场景:

  1. 数据依赖规则

as-if-serial原则:不管如何重排序,结果不能变

JVM语言级别的内存屏障:

loadload、storestore、loadstore、storeload、

Happens-Before规则
  1. 程序顺序规则:单线程对于结果来说是不变的
  2. volatile变量规则:volatile修饰的变量,写操作对于后续读操作可见
  3. 传递性规则:1 h-b 2,2 h-b 3,则1 h-b 3
  4. start规则:线程a中启动b线程,则启动前的数据对于线程b可见
  5. join规则:线程b join 线程a,则线程a的共享变量操作对于b可见
  6. 监视器锁规则:对于锁(synchronized)的解锁对于这个锁的加锁可见,即多线程之间synchronized代码块之间是顺序可见
  7. 线程中断规则:对线程interrupt方法的调用happend-before线程中断检测
  8. 对象创建规则:对象初始化完成happend-before它的finalize()方法

常见问题:

MESI协议实现可见性和有序性:

无法完全实现。由于MESI store buffer当中是异步的,可能会造成有序性问题,因此通过内存屏障来解决

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值