如何理解 Java 语言规范中的 volatile 示例?
how to understand volatile example in Java Language Specification?
我认为 Java 规范中的 volatile 示例有点错误。
在 8.3.1.4 中。 volatile 字段,它说
class Test {
static int i = 0, j = 0;
static void one() { i++; j++; }
static void two() {
System.out.println("i=" + i + " j=" + j);
}
}
...then method two could occasionally print a value for j that is greater than the value of i, because the example includes no synchronization and, under the rules explained in§17.4, the shared values of i and j might be updated out of order.
我认为即使这些更新是有序的,方法二仍然可能看到 j 大于 i,因为 System.out.println("i=" + i + " j=" + j)
不是原子的,并且 i 在 j 之前被读取。
方法二同
read i
read j
所以有可能
read i
i++
j++
read j
在这种情况下,方法二看到 j 的值大于 i,但是更新并没有乱序。
因此乱序并不是看到 j > i
的唯一原因
应该是System.out.println("j=" + j + " i=" + i);
吗?
这次乱序是看到j > i的唯一原因
这些例子不仅仅是“有点错误”。
首先,您是对的,即使不重新排序,在此示例中 j
也可能大于 i
。这甚至在稍后 the same example:
中得到承认
Another approach would be to declare i
and j
to be volatile
:
class Test {
static volatile int i = 0, j = 0;
static void one() { i++; j++; }
static void two() {
System.out.println("i=" + i + " j=" + j);
}
}
This allows method one
and method two
to be executed concurrently, but guarantees that accesses to the shared values for i
and j
occur exactly as many times, and in exactly the same order, as they appear to occur during execution of the program text by each thread. Therefore, the shared value for j
is never greater than that for i
, because each update to i
must be reflected in the shared value for i
before the update to j
occurs. It is possible, however, that any given invocation of method two
might observe a value for j
that is much greater than the value observed for i
, because method one
might be executed many times between the moment when method two
fetches the value of i
and the moment when method two
fetches the value of j
.
当然,“j
的共享值永远不会大于i
”的说法很深奥,只是说对了在下一句中“有可能……[观察] j
的值远大于 i
的观察值”。
所以 j
永远不会大于 i
,除非观察到 比 i
大 很多?难道说“再大一点”是不可能的吗?
当然不是。这种说法毫无意义,似乎是试图将某些 objective 事实(例如“共享值”)与“观察到的值”分开的结果,而实际上,程序中只有可观察到的行为。
这是用错句说明的:
This allows method one and method two to be executed concurrently, but guarantees that accesses to the shared values for i
and j
occur exactly as many times, and in exactly the same order, as they appear to occur during execution of the program text by each thread.
即使有 volatile
个变量,也没有这样的保证。 JVM 必须保证的是 观察到的行为 不与规范相矛盾,因此当您在循环中调用 one()
千次时,例如,优化器可能如果它可以排除另一个线程见证这种优化存在的可能性(除了从更高的速度推断),仍然用一个原子增量替换它。
或者换句话说,一个变量(分别是它的内存位置)实际被访问了多少次,是不可观察的,因此没有指定。反正没关系。对于应用程序程序员来说,重要的是 j
可以大于 i
,无论变量是否声明为 volatile
。
在 two()
中交换 i
和 j
的读取顺序可能会使它成为一个更好的例子,但我认为,如果 JLS §8.3,那将是最好的。 1.2并没有试图通俗地解释volatile
的含义,只是声明它根据the memory model强加了特殊的语义,并留给JMM以正式正确的方式解释。
程序员不应该仅仅通过阅读 8.3.1.4. 来掌握并发性,所以这里的例子毫无意义(在最好的情况下;最坏的情况会造成这个例子足以理解问题的印象) .
霍尔格在他的回答中所说的是绝对正确的(再次阅读 并接受它),我只想使用 jcstress, this is even sort of easy to prove. The test itself is just a minor refactor from the Coherence Sample 补充一点(非常棒!IMO):
import org.openjdk.jcstress.annotations.Actor;
import org.openjdk.jcstress.annotations.Expect;
import org.openjdk.jcstress.annotations.JCStressTest;
import org.openjdk.jcstress.annotations.Outcome;
import org.openjdk.jcstress.annotations.State;
import org.openjdk.jcstress.infra.results.II_Result;
@JCStressTest
@Outcome(id = "0, 1", expect = Expect.ACCEPTABLE_INTERESTING, desc = "only j updated")
@Outcome(id = "1, 0", expect = Expect.ACCEPTABLE_INTERESTING, desc = "only i updated")
@Outcome(id = "0, 0", expect = Expect.ACCEPTABLE_INTERESTING, desc = "both updates lost")
@Outcome(id = "1, 1", expect = Expect.ACCEPTABLE, desc = "both updated")
@State
public class SOExample {
private final Holder h1 = new Holder();
private final Holder h2 = h1;
@Actor
public void writeActor() {
++h1.i;
++h1.j;
}
@Actor
public void readActor(II_Result result) {
Holder h1 = this.h1;
Holder h2 = this.h2;
h1.trap = 0;
h2.trap = 0;
result.r1 = h1.i;
result.r2 = h2.j;
}
static class Holder {
int i = 0;
int j = 0;
int trap;
}
}
即使您不理解代码,重点是 运行 它会显示 ACCEPTABLE_INTERESTING
绝对可能的结果;有 volatile int i = 0; volatile int j = 0;
或没有 volatile
。
我认为 Java 规范中的 volatile 示例有点错误。
在 8.3.1.4 中。 volatile 字段,它说
class Test {
static int i = 0, j = 0;
static void one() { i++; j++; }
static void two() {
System.out.println("i=" + i + " j=" + j);
}
}
...then method two could occasionally print a value for j that is greater than the value of i, because the example includes no synchronization and, under the rules explained in§17.4, the shared values of i and j might be updated out of order.
我认为即使这些更新是有序的,方法二仍然可能看到 j 大于 i,因为 System.out.println("i=" + i + " j=" + j)
不是原子的,并且 i 在 j 之前被读取。
方法二同
read i
read j
所以有可能
read i
i++
j++
read j
在这种情况下,方法二看到 j 的值大于 i,但是更新并没有乱序。
因此乱序并不是看到 j > i
的唯一原因应该是System.out.println("j=" + j + " i=" + i);
吗?
这次乱序是看到j > i的唯一原因
这些例子不仅仅是“有点错误”。
首先,您是对的,即使不重新排序,在此示例中 j
也可能大于 i
。这甚至在稍后 the same example:
Another approach would be to declare
i
andj
to bevolatile
:class Test { static volatile int i = 0, j = 0; static void one() { i++; j++; } static void two() { System.out.println("i=" + i + " j=" + j); } }
This allows method
one
and methodtwo
to be executed concurrently, but guarantees that accesses to the shared values fori
andj
occur exactly as many times, and in exactly the same order, as they appear to occur during execution of the program text by each thread. Therefore, the shared value forj
is never greater than that fori
, because each update toi
must be reflected in the shared value fori
before the update toj
occurs. It is possible, however, that any given invocation of methodtwo
might observe a value forj
that is much greater than the value observed fori
, because methodone
might be executed many times between the moment when methodtwo
fetches the value ofi
and the moment when methodtwo
fetches the value ofj
.
当然,“j
的共享值永远不会大于i
”的说法很深奥,只是说对了在下一句中“有可能……[观察] j
的值远大于 i
的观察值”。
所以 j
永远不会大于 i
,除非观察到 比 i
大 很多?难道说“再大一点”是不可能的吗?
当然不是。这种说法毫无意义,似乎是试图将某些 objective 事实(例如“共享值”)与“观察到的值”分开的结果,而实际上,程序中只有可观察到的行为。
这是用错句说明的:
This allows method one and method two to be executed concurrently, but guarantees that accesses to the shared values for
i
andj
occur exactly as many times, and in exactly the same order, as they appear to occur during execution of the program text by each thread.
即使有 volatile
个变量,也没有这样的保证。 JVM 必须保证的是 观察到的行为 不与规范相矛盾,因此当您在循环中调用 one()
千次时,例如,优化器可能如果它可以排除另一个线程见证这种优化存在的可能性(除了从更高的速度推断),仍然用一个原子增量替换它。
或者换句话说,一个变量(分别是它的内存位置)实际被访问了多少次,是不可观察的,因此没有指定。反正没关系。对于应用程序程序员来说,重要的是 j
可以大于 i
,无论变量是否声明为 volatile
。
在 two()
中交换 i
和 j
的读取顺序可能会使它成为一个更好的例子,但我认为,如果 JLS §8.3,那将是最好的。 1.2并没有试图通俗地解释volatile
的含义,只是声明它根据the memory model强加了特殊的语义,并留给JMM以正式正确的方式解释。
程序员不应该仅仅通过阅读 8.3.1.4. 来掌握并发性,所以这里的例子毫无意义(在最好的情况下;最坏的情况会造成这个例子足以理解问题的印象) .
霍尔格在他的回答中所说的是绝对正确的(再次阅读 并接受它),我只想使用 jcstress, this is even sort of easy to prove. The test itself is just a minor refactor from the Coherence Sample 补充一点(非常棒!IMO):
import org.openjdk.jcstress.annotations.Actor;
import org.openjdk.jcstress.annotations.Expect;
import org.openjdk.jcstress.annotations.JCStressTest;
import org.openjdk.jcstress.annotations.Outcome;
import org.openjdk.jcstress.annotations.State;
import org.openjdk.jcstress.infra.results.II_Result;
@JCStressTest
@Outcome(id = "0, 1", expect = Expect.ACCEPTABLE_INTERESTING, desc = "only j updated")
@Outcome(id = "1, 0", expect = Expect.ACCEPTABLE_INTERESTING, desc = "only i updated")
@Outcome(id = "0, 0", expect = Expect.ACCEPTABLE_INTERESTING, desc = "both updates lost")
@Outcome(id = "1, 1", expect = Expect.ACCEPTABLE, desc = "both updated")
@State
public class SOExample {
private final Holder h1 = new Holder();
private final Holder h2 = h1;
@Actor
public void writeActor() {
++h1.i;
++h1.j;
}
@Actor
public void readActor(II_Result result) {
Holder h1 = this.h1;
Holder h2 = this.h2;
h1.trap = 0;
h2.trap = 0;
result.r1 = h1.i;
result.r2 = h2.j;
}
static class Holder {
int i = 0;
int j = 0;
int trap;
}
}
即使您不理解代码,重点是 运行 它会显示 ACCEPTABLE_INTERESTING
绝对可能的结果;有 volatile int i = 0; volatile int j = 0;
或没有 volatile
。