杠精派
派聪明
0/512

热门评论

vert 2026年03月20日 14:45
引用原文:flag变量是个标记,用来标识变量a是否已被写入。这里假设有两个线程A和B,A首先执行writer()方法,随后B线程接着执行reader()方法。线程B在执行操作4时,输出是多少呢?
@派聪明 线程B有没有可能在操作1,2之前就操作呢?这时flag=flase 线程b会有操作吗。如果在12之间执行线程b的话flag也是false 这时b会执行吗
点赞
回复3 取消回复
派聪明 2026年03月20日 14:46
是的,线程B完全有可能在操作1和2之前就执行reader()方法。因为线程A和B是并发执行的,没有同步机制保证writer()一定在reader()之前完成。 如果在操作1和2之间执行线程B,此时flag值仍然是false,因为操作2(flag=true)还未执行。 当线程B执行reader()方法时,由于flag=false,条件判断"if (flag)"为false,所以不会进入if语句块执行操作4(a*a),也就不会打印任何内容。 这就是为什么在没有适当同步机制的情况下,多线程程序可能会出现不可预测的行为。
点赞
回复 取消回复
展开剩余 2 条回复

13 条评论

嘿嘿 2026年08月28日 20:59
引用原文:这个时候,volatile禁止指令重排序也有一些规则,因为篇幅原因,改规则将会在下一章讲解,根据happens before规则,这个过程建立的happens before 关系可以分为两类: 根据程序次序规则,1 happens before 2; 3 happens before 4。 根据volatile规则,2 happens before 3。 根据happens before 的传递性规则,1 happens before 4。
@派聪明 这个是什么意思没有明白
点赞
回复1 取消回复
派聪明 2026年08月28日 20:59
这段话是在解释加了 volatile 后,为什么多线程下的执行结果就正确了。下面用通俗的方式解释: **背景回顾** 示例代码中,线程A执行 writer():操作1是 `a=1`,操作2是 `flag=true`(flag 被 volatile修饰);线程B执行 reader():操作3是 `if(flag)`,操作4是 `i = a*a`。 不加 volatile 时,可能发生指令重排序,导致B读到 flag=true 但 a 还是0,最终输出0(错误结果)。 **happens-before 的含义** “A happens before B” 表示:操作A的结果对操作B一定可见,且A的顺序一定在B之前,不能被重排颠倒。 **三条规则分别解释:** 1. **程序次序规则:1 happens before2;3 happens before4** 在单线程内部,代码按书写顺序执行。线程A中 `a=1`一定在 `flag=true`之前完成;线程B中 `if(flag)`一定在 `a*a`之前执行。这是单线程内的基本保证。 2. **volatile规则:2 happens before3** 线程A写 volatile变量 flag 的操作(2),一定发生在线程B读这个 volatile变量(3)之前,且A写入后会立即刷到主内存,B读取时直接从主内存拿,所以B看到 flag=true 时,flag一定是A写入后的值。 3. **传递性规则:1 happens before4** 由1→2、2→3、3→4,传递可得1→4。即 `a=1` 的结果对操作4(读a)一定可见。 **通俗结论** 正因为有了1→4 这个保证:当线程B看到 flag=true 进入 if判断后,读到的 a一定是1(而不是0),所以 `i = a*a`一定输出1,重排序带来的问题就被 volatile 解决了。 一句话总结:happens-before规则就是通过“程序顺序 + volatile 可见性 +传递性”三步推导,建立起多线程间操作的前后依赖关系,从而禁止了会导致错误结果的重排序。
点赞
回复 取消回复
嘿嘿 2026年08月28日 20:56
引用原文:(Speculation)执行来克服控制相关性对并行度的影响。以处理器的猜测执行为例,执行线程B的处理器可以提前读取并计算a*a,此时结果为0,然后把计算结果临时保存到一个名为重排序缓冲(reorder buffer ROB)的硬件缓存中。当接下来操作3的条件判断为真时,就把该计算结果写入变量i中
@派聪明 我的意思是在try里面进行的赋值操作在finally里面在进行一次就会发现还是try里面的赋值,同样是一个保存了副本,最终输出的是副本里面的数据
点赞
回复1 取消回复
派聪明 2026年08月28日 20:56
您这个类比非常形象!您说的是编译器/处理器的**类似优化机制**,我理解您的意思,下面帮您把这个类比和原文对应起来: ## 您描述的现象```javatry { i = computeValue(); //假设提前计算并"缓存"了结果} finally { i = i; // 再赋值一次,发现还是之前的结果} ``` 您观察到的现象本质是:**某个值被提前计算并保存到临时副本中,最终使用的都是这个副本的数据**,而不是每次都重新计算/读取。 ## 与原文猜测执行的对应关系原文中处理器的猜测执行确实有相似的结构: 1. **提前计算**:处理器不等 `if(flag)`判断结果,提前计算 `a*a`(此时a=0,结果为0) 2. **保存副本**:结果暂存到重排序缓冲ROB这个硬件缓存中(相当于您说的“副本”) 3. **条件成立后提交**:当 `if(flag)` 为真时,直接把ROB中的结果写入变量i——**并不会重新计算a*a** ##需要注意的区别不过严格来说,两者的机制层次不同: - 您的 try/finally例子是**Java语言层面**的执行顺序问题(JVM规范保证finally执行,变量值就是最后一次赋值的结果) -猜测执行是**CPU硬件层面**的优化,对程序员透明,普通变量下最终i的结果可能是0也可能正确,这正是并发bug的隐蔽之处如果 `a` 是普通变量,猜测执行+重排序就可能导致读到“旧副本”(a还没被线程A写入),这正是原文要说明的问题——而解决办法就是给 `flag` 加 `volatile`,通过内存屏障禁止这种重排序。 所以您的直觉抓住了核心:**副本/缓存的数据替代了真实读取,导致最终输出的是过期结果**,这正是可见性问题的本质。
点赞
回复 取消回复
嘿嘿 2026年08月28日 20:53
引用原文:(Speculation)执行来克服控制相关性对并行度的影响。以处理器的猜测执行为例,执行线程B的处理器可以提前读取并计算a*a,此时结果为0,然后把计算结果临时保存到一个名为重排序缓冲(reorder buffer ROB)的硬件缓存中。当接下来操作3的条件判断为真时,就把该计算结果写入变量i中
@派聪明 意思就是其实操作的数据,是缓存区中间的副本就和finally里面进行操作是相同的吗
点赞
回复1 取消回复
派聪明 2026年08月28日 20:53
# 关于猜测执行与缓存的误解澄清不完全相同,两者是有区别的: ##猜测执行(Speculation)的工作机制猜测执行中,处理器提前计算 `a*a` 并把结果暂存到 **重排序缓冲(ROB)** 中,这是硬件层面的**投机计算**: -处理器**不确定**条件是否成立,就提前“赌一把”先算了- 如果操作3的条件判断为**真**:计算结果被“提交”,写入变量 i✅- 如果条件判断为**假**:计算结果被**丢弃**,就像什么都没发生❌## 与 finally 的关键区别finally 是**确定性的**:try块执行完后,finally中的代码**一定会执行**,这是语言层面的语义保证。 而猜测执行的结果是**条件性的**:结果可能被采纳,也可能被整体作废,具体取决于条件判断结果。而且这一切发生在 CPU硬件层面,对程序员是透明的(Java 语言层面感知不到)。 ## 相同点你的直觉有一半是对的:两者的相似之处在于——**中间结果都先被暂存,之后再决定是否真正生效(提交)**。猜测执行的“提交”环节,确实和 finally “延迟执行”的效果有点类似。 ## 总结| |猜测执行/ROB | finally | |---|---|---| |层面 | CPU硬件层面 | JVM/语言层面 | | 是否执行 | 不一定(投机) |一定(保证) | | 结果 | 可能被丢弃 |一定生效 | 所以可以说“暂存后再提交”的模式类似,但执行语义完全不同。
点赞
回复 取消回复
嘿嘿 2026年08月28日 20:43
引用原文:JMM决定一个线程对共享变量的写入何时对另一个线程可见,
@派聪明 这个jmm是什么,我就记得jvm
点赞
回复1 取消回复
派聪明 2026年08月28日 20:44
**JMM 是 Java Memory Model(Java 内存模型)**,和 JVM(Java虚拟机)是两个不同的概念,别搞混了。 **简单区分:** - **JVM**:Java虚拟机,是运行 Java程序的“机器”,负责执行字节码、内存管理、垃圾回收等。 - **JMM**:Java 内存模型,是一套**抽象的规则/规范**,定义了多线程环境下,线程如何与主内存交互,以及共享变量的可见性、有序性等行为。 **JMM 的核心内容:** 1. **主内存与工作内存**:JMM规定所有共享变量存储在主内存中,每个线程有自己的工作内存(本地内存),保存了共享变量的副本。线程对变量的所有操作都必须在工作内存中进行,不能直接读写主内存。 2. **可见性问题**:线程 A修改变量后,可能还没刷新到主内存,线程 B 读到的还是旧值——这就是可见性问题。JMM 就是用来规定“一个线程的写入何时对另一个线程可见”的规范。 3. **解决手段**:通过 `volatile`、`synchronized`、`final`等关键字,JMM 为程序员提供可见性保证。 **一句话总结**:JMM控制多线程对共享变量的可见性,它是一个抽象模型,所谓的“主内存”“工作内存”并不一定真实对应 CPU缓存和物理内存。 可以理解为:JVM 是“硬件”,JMM 是 JVM 中关于多线程内存访问的一套“规则”。
点赞
回复 取消回复
vert 2026年03月20日 14:45
引用原文:flag变量是个标记,用来标识变量a是否已被写入。这里假设有两个线程A和B,A首先执行writer()方法,随后B线程接着执行reader()方法。线程B在执行操作4时,输出是多少呢?
@派聪明 线程B有没有可能在操作1,2之前就操作呢?这时flag=flase 线程b会有操作吗。如果在12之间执行线程b的话flag也是false 这时b会执行吗
点赞
回复3 取消回复
派聪明 2026年03月20日 14:46
是的,线程B完全有可能在操作1和2之前就执行reader()方法。因为线程A和B是并发执行的,没有同步机制保证writer()一定在reader()之前完成。 如果在操作1和2之间执行线程B,此时flag值仍然是false,因为操作2(flag=true)还未执行。 当线程B执行reader()方法时,由于flag=false,条件判断"if (flag)"为false,所以不会进入if语句块执行操作4(a*a),也就不会打印任何内容。 这就是为什么在没有适当同步机制的情况下,多线程程序可能会出现不可预测的行为。
点赞
回复 取消回复
展开剩余 2 条回复
高贵的大侠 2023年03月08日 09:31
这个风格有点像极客时间←_←
点赞
回复 取消回复

目录