我认为Java规范中volatile的示例有点错误。
在8.3.1.4中。
class Test {
static int i = 0, j = 0;
static void one() { i++; j++; }
static void two() {
System.out.println("i=" + i + " j=" + j);
}
}
...然后方法二可能偶尔会打印j的值,该值大于i的值,因为该示例不包含同步,并且在第17.4节中解释的规则下,i和j的共享值可能是更新顺序不正确。
我认为即使这些更新是按顺序进行的,方法二仍可能看到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
的唯一原因答案 0 :(得分:4)
这些示例不仅仅是“有点错误”。
首先,您是正确的,即使在没有重新排序的情况下,本示例中j
的显示可能也大于i
。甚至在以后的the same example中也承认了这一点:
另一种方法是将
i
和j
声明为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); } }
这允许方法
one
和方法two
同时执行,但保证对i
和j
的共享值的访问发生的次数完全相同,并且它们的顺序完全相同,就像它们在每个线程执行程序文本期间所发生的一样。因此,j
的共享值永远不会大于i
的共享值,因为在更新为{之前,对i
的每次更新都必须反映在i
的共享值中{1}}发生。但是,有可能方法j
的任何给定调用都可能观察到two
的值,该值远大于j
观察到的值,因为方法i
可能会在方法one
获取two
的时刻到方法i
获取two
的时刻之间执行了多次。
当然,可以说“ j
的共享价值永远不比j
的共享价值”,只是在下一个句子中说“ 可能[…]观察到的i
的值比观察到的j
的值大得多。”
所以i
永远不会比j
大,除非它被观察到比i
大?是否应该说“再大一点”是不可能的?
当然不是。该语句没有任何意义,似乎是试图将诸如“共享价值”与“观察价值”之类的客观事实区分开来的结果,而实际上,程序中只有可观察到的行为。
这用错误的句子来说明:
这允许方法一和方法二同时执行,但是保证对
i
和i
的共享值的访问发生的次数和次数完全相同,并且顺序完全相同似乎发生在每个线程执行程序文本期间。
即使具有j
变量,也无法保证。 JVM必须保证的是,观察到的行为不会与规范相矛盾,因此,例如,当您在循环中调用volatile
千次时,优化器可能仍会替换它如果可以排除另一个线程见证这种优化的可能性(不是从更高的速度中推论出来的话),那么原子数就增加一千。
或者换句话说,变量(实际上是其存储位置)实际被访问的次数是不可观察的,因此未指定。没关系。对于应用程序程序员而言,重要的是,无论变量是否声明为one()
,j
都可以大于i
。
在volatile
中交换i
和j
的读取顺序可能是一个更好的示例,但我认为,如果JLS§8.3.1.2做到了,那将是最好的选择并不是试图用口语来解释two()
的含义,而是只是说它根据the memory model施加了特殊的语义,并将其留给JMM以形式上正确的方式进行解释。
程序员不应仅通过阅读8.3.1.4来掌握并发性,因此此处的示例毫无意义(最好的情况;最坏的情况是给人以示例足以理解问题的印象)。 / p>
答案 1 :(得分:0)
霍尔格在回答中说的是绝对正确的(再次阅读并接受),我只想用jcstress来补充,这甚至很容易证明。测试本身只是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
。