@JCStressTest
@State
@Outcome(id = "10", expect = ACCEPTABLE, desc = "Boring")
@Outcome(id = {"0", "1"}, expect = FORBIDDEN, desc = "Boring")
@Outcome(id = {"9", "8", "7", "6", "5"}, expect = ACCEPTABLE, desc = "Okay")
@Outcome( expect = ACCEPTABLE_INTERESTING, desc = "Whoa")
public static class Volatiles {
volatile int x;
@Actor
void actor1() {
for (int i = 0; i < 5; i++) {
x++;
}
}
@Actor
void actor2() {
for (int i = 0; i < 5; i++) {
x++;
}
}
@Arbiter
public void arbiter(I_Result r) {
r.r1 = x;
}
}作者强调,由于增量不是原子操作,因此不能期望每次循环迭代“丢失”一个增量更新。因此,所有的结果(直到10),除了0和1,都是允许的(而且确实会发生)。
我明白了为什么不允许0:在初始化对象的默认值和每个线程中的第一个动作之间有一个HB边,如JLS中所述。JLS 17.4.4
对每个变量的默认值(零、假或空)的写入与每个线程中的第一个操作同步。
作者还解释了如何获得结果2
The most interesting result, "2" can be explained by this interleaving:
Thread 1: (0 ------ stalled -------> 1) (1->2)(2->3)(3->4)(4->5)
Thread 2: (0->1)(1->2)(2->3)(3->4) (1 -------- stalled ---------> 2)我知道你不能这样“扩展”上面的解释:
Thread 1: (0 --------- stalled -----------> 1) (1->2)(2->3)(3->4)(4->5)
Thread 2: (0->1)(1->2)(2->3)(3->4)(4->5)因为它会导致结果5。但是,难道没有一种将产生1的执行吗?我绝对想不出任何东西。但为什么真的没有呢?
发布于 2022-09-11 22:08:25
首先,让我们记住volatile是如何在Java中工作的。
在Java中,所有易失性读和写都以运行时的全局顺序进行(即JLS 17.4.4中的同步顺序)。
这个全局顺序的属性:
易失性读取总是返回对此变量(即JLS 17.4.4中的同步)的最后一个(按此全局顺序)易失性写入。
其次,让我们澄清“增量不是原子操作”意味着x++由3个原子操作组成:
var temp = x; // volatile read of 'x' to local variable 'temp'
temp = temp + 1; // increment of local variable 'temp'
x = temp; // volatile write to `x`最后,让我们重写
The most interesting result, "2" can be explained by this interleaving:
Thread 1: (0 ------ stalled -------> 1) (1->2)(2->3)(3->4)(4->5)
Thread 2: (0->1)(1->2)(2->3)(3->4) (1 -------- stalled ---------> 2)以显示易失性读和写到x的方式
Thread1 Thread2
r₁₀:0 | global
r₂₀:0 | order of
w₂₁:1 | volatile
r₂₂:1 | reads
w₂₃:2 | and
r₂₄:2 | writes
w₂₅:3 V
r₂₆:3
w₂₇:4
w₁₁:1
r₂₈:1
r₁₂:1
w₁₃:2
r₁₄:2
w₁₅:3
r₁₆:3
w₁₇:4
r₁₈:4
w₁₉:5
w₂₉:2在这个图表上:
r:1是x的易失读,它返回1w:1是1到x的易失性写入请注意:
w总是在同一个线程中写入前一个r的值,该线程的增量为1。r总是以全局顺序返回由前一个w编写的值。现在我们可以回答你的问题了:
但是,难道没有一种将产生
1的执行吗?我绝对想不出任何东西。但为什么真的没有呢?
w₂₉中的最后一次写入:它总是写入(r₂₈读取的值)+1
(顺便说一下,全局顺序中的最后一次写总是Thread1中的最后一次写w₂₉,或者是Thread2中的最后一次写w₂₉;在本例中是w₂₉,但是对于w₁₉来说,推理是一样的)r₂₈总是按照全局顺序读取前面的写,即:- either `w₂₇` from the same thread
- or some write `w₁ₓ` from Thread1 (if `w₁ₓ` happens in between `w₂₇` and `r₂₈` in runtime)在任何情况下,r₂₈总是返回一些以前的非初始写。
但是每个非初始的写总是至少写1。
这意味着w₂₉总是至少编写2。
https://stackoverflow.com/questions/73671449
复制相似问题