线程安全性之有序性和内存屏障
2021/12/6 7:18:55
本文主要是介绍线程安全性之有序性和内存屏障,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!
有序性问题
通过上篇文章我们得知程序在CPU中是以指令的形式执行的。
本篇文章有序性问题
也称cpu指令重排序
1.CPU指令重排序
在CPU缓存优化过程中引入了StoreBuffer,虽说优化了性能,但也出现了新的问题,先看一段代码
static int x = 0, y = 0; static int a = 0, b = 0; public static void main(String[] args) throws InterruptedException { int i = 0; for (; ; ) { i++; x = 0;y = 0;a = 0;b = 0; Thread t1 = new Thread(() -> { a = 1; x = b; }); Thread t2 = new Thread(() -> { b = 1; y = a; }); t1.start();t2.start(); t1.join();t2.join(); if (x == 0 && y == 0) { System.out.println("第" + i + "次:x=" + x + ",y=" + y); break; } } } //我电脑上执行 第161581次:x=0,y=0
仔细看上诉代码,正常来说只有三个结果:[10],[01],[11],但是为什么会出现[00]呢?
这就是典型的指令重排序了,等于执行时变成了 x = b;a = 1; y = a;b = 1;
了
2.怎么导致重排序的
//例如这段代码 int a = 0; function(){ a = 1; b = a+1; assert(b == 2); //false } //指令重排序 b = a+1; a = 1;
- 再看一张图讲解
多线程情况下步骤讲解:
- CPU0执行
a = 1
,发现并没有加载a,a在共享状态下(CPU1和CPU2下共享),需要把其他CPU的缓存读取过来并置为失效状态,最终完成后也就到了第二步a=0/E
,此时a在其他CPU处于失效状态,所以在CPU1下是独占状态。 - 由于是store buffer同步到cache是必须要等待到其他CPU都同步完成才会继续,可能存在的情况是先执行到
b=a+1
了,此时b没有被加载,所以b=0/E
是独占状态,接下来第4步,此时a还在异步等待,b就变成b=0+1 -> b=1/M
修改状态 - 最后第5步a终于执行完毕再设置
a=1
,但此时b=a+1
已经执行完毕,所以就导致了指令重排序问题
3.CPU性能优化博弈图
进行再次优化,引入了 invalidate queue
失效队列,但由于失效队列是异步处理的,还是会有此问题存在,此问题CPU层面已无法解决,于是提供内存屏障指令,由开发者根据需求使用
5.怎么解决指令重排序
加入内存屏障其实也就是#Lock指令,它既能实现缓存锁/总线锁也能实现内存屏障
内存屏障
1.什么是内存屏障
为什么需要开发者实现?因为CPU层面不知道什么时候允许优化,什么时候不允许优化
- 读屏障(lfence) load 读操作必须在写操作之前完成
- 写屏障(sfence)
- 全屏障(mfence)
在liunx上分别对应方法
- smp_rmb
- smp_wmb
- smp_mb
接下来看这个代码
int a = 0; function(){ a = 1; //读屏障 b=a+1 必须要在a=1之后执行 smp_rmb(); b = a+1; assert(b == 2); //false }
为此定义了一种抽象模型,即JMM模型
2.JMM内存屏障模型
JAVA线程去访问内存的一个规范,它是一种抽象模型,解决有序性可见性问题(关键字)
不同的CPU架构不同的汇编指令,这个就是对不同操作操作系统添加内存屏障的封装,提供以下方法,具体源码在hotspot中的orderAccess_操作系统
中实现
屏障类型 | 指令示例 | 说明 |
---|---|---|
LoadLoad Barriers | Load1;LoadLoad;Load2 | 确保Load1数据装载优先于Load2及所有后续的装载指令 |
StoreStore Barriers | Store1;LoadLoad;Store2 | 确保Store1数据刷新到内存优先于Store2及所有后续的存储指令 |
LoadStore Barriers | Load1;LoadLoad;Store2 | 确保Load1数据装载优先于Store2刷新到内存指令及所有后续的存储指令 |
StoreLoad Barriers | Store1;LoadLoad;Load2 | 确保Store1数据刷新到内存优先于Load2及所有后续的装载指令 |
3.happends-before规则
- 程序顺序型规则 ,单线程执行结果一定不会发生变化
- 传递性规则,a happends before b,b happends before c,a happends before c
- volatile规则
- 监视器规则,锁的释放一直在执行结果之后
- start规则,线程启动之前的数值,在线程执行后一定是新的数值,不存在可见性问题
- join规则,线程执行结果一定在这个之前
happends-before规则就是为了描述可见性规则
线程安全性中的可见性和有序性总结
可见性导致的问题
- CPU高速缓存
- 指令重排序
使用synchronized volatile finanl关键字加锁保证可见性。
提供内存屏障指令,保证程序不会出现可见性,有序性问题
以上就是本章的全部内容了。
上一篇:线程安全性之可见性、缓存一致性(MESI)以及伪共享问题分析
下一篇:J.U.C ReentrantLock可重入锁使用以及源码分析
云想衣裳花想容,春风拂槛露华浓
这篇关于线程安全性之有序性和内存屏障的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!
- 2024-06-15matplotlib作图不显示3D图,怎么办?
- 2024-06-1503-Loki 日志监控
- 2024-06-1504-让LLM理解知识 -Prompt
- 2024-06-05做软件测试需要懂代码吗?
- 2024-06-0514-ShardingSphere的分布式主键实现
- 2024-06-03为什么以及如何要进行架构设计权衡?
- 2024-05-31全网首发第二弹!软考2024年5月《软件设计师》真题+解析+答案!(11-20题)
- 2024-05-31全网首发!软考2024年5月《软件设计师》真题+解析+答案!(21-30题)
- 2024-05-30【Java】百万数据excel导出功能如何实现
- 2024-05-30我们小公司,哪像华为一样,用得上IPD(集成产品开发)?