关于内存一致性, 指令重排,并发3要素
来源:5-2 竟然被问Java 9的setRelease和getAcquire和volatile的区别?

上游猴子
2021-09-26
老师, 针对内存一致性,指令重排, 并发3要素的一些知识点,存在很多理解出入的点。
首先我说下我的理解。 并发的3要素,**原子性,可见性, 顺序性**
- 原子性
i++ , 并发下, i的获取到 i的计算更新,中间可能穿插其他线程对 i 的操作, 所以原子性是保证 i的获取到i的计算是一个原子操作
- 可见性(内存一致性)
不同观察者在同个时间线上看到的内容是一致的
i = 0;
// 线程1(核1)
i = 10; (语句1)
// 线程2(核2)
b = i + i; (语句2)
前提是我们已经保证了先执行语句1, 在执行语句2(比如利用锁机制保证)。
分析个场景, i=10写入到内存中,此时并未同步到核2的cpu缓存中, 从观察者角度来分析, 在这个时刻线程1会看到i是10,线程2看到i是0, 从而导致 b = 0 + 0, 并不是我们期望的 b = 10 + 10
- 顺序性(指令重排)
可以基于工具控制多线程下逻辑上顺序执行(synchronized,lock,cas), 由于指令重排, 实际底层指令执行并非顺序, 这也就导致了多线程下逻辑不可控, 所以有序性是指阻止部分指令重排保证指令的有序执行
boolean switch = false
Connect connect = new Connect();
// 线程1
connect = initConnect(); // 语句1
switch = true; // 语句2
// 线程2
while(!switch) {Thread.sleep(0)};
connect.execute(xxx);
从程序上看,如果代码顺序执行, 程序是能正常被执行的。
但是由于指令重排, 语句1和语句2重排了, switch = true 先执行, while 跳出循环, 执行 connect.execute , 此时 connect 还是个空的初始化对象 , 并未初始化完毕. 程序就会出错了。
问题1:对有序性的定义是指任意时刻观察到的历史是一致的?
翻看了很多资料,我理解的有序性是指并发下底层的指令执行不会破坏逻辑上顺序执行。
可见性是指不同观察者在同个时刻看到的东西是一致的,更符合你说的概念。
问题2:内存一致性模型分析?
分析一: 多线程下, 程序逻辑是一个有序的执行, 线程1写入的内容并未同步到线程2的cpu缓存,导致线程1和线程2此刻观察到的内容是不一致的
分析二: 指令重排的分析?
if (a==0) {// some code …} return a;
a == 0 和 return a 发生指令重排, 中间会穿插线程改变 a 的值, 导致程序执行出错. 这个出错和指令重排并没有关系呀, 这可能是由于写可能发生在两次读(a==0 和 return a 对a 的读取)的中间,或者是说由于写入后未及时同步到 cpu 缓存导致
假设通过锁保证他顺序,其实就又回到分析一的情况了
问题点3:指令重排是触发内存不一致的原因?
如上, connnect.execute 对 connect 的读原本是发生在initConnect 写之后的, 但由于指令重排, 导致读的动作发生在写之前了, 程序出错。
我们站在 connnect.execute 执行时刻来观察, 线程2看到 connnect 和 线程1看到的 connect 是一致的(因为此刻还未发生initConnect写,读的还是 new Connect()这个版本的connect), 所以他是内存一致的.
我们站在 connect = initConnect() 时刻来观察, 线程2 看到 connect 和 线程N看到的 connect 可能会不一致, 比如线程2写入到内存中但还并未同步到线程N对应的cpu核缓存中, 此时就会产生内存不一致, 但不是由于指令重排导致的呀。
1回答
-
求老仙
2021-10-06
原子性的理解正确。
可见性理解错了。 可见性指一个线程的行为,可以被另一个线程观察到。 比如线程A修改了i的值,线程B可以观测到。为什么会有可见性问题? 这是因为存储器分级策略,而Volatile就是保证可见性的手段。
有序性也理解错了。有序性指可以确定所有操作的先后顺序。也就是不存在真正同时执行的两个i++。它们一定有顺序,而且可以确定这种顺序。而不是结果取决于执行的时序(而是可以预见!~)
有序性不可能绝对保证,没有绝对的有序,除非你不使用异步。happens-before规则在保证了不完全的(Partial)的有序。
顺序是并发问题的核心,原子操作不可分,因此不考虑顺序。可见性是有序性的结果。Partial的有序性,可以保证Partial的可见性。 比如Volatile需要阻止指令重排,这让volatile的写入和读取间产生了顺序。
00
相似问题