想象一下,在我的并发应用程序中,我有这样一个Java类(非常简化):
更新:
public class Data {
static Data instance;
final int[] arr;
public Data() {
arr = new int[]{1, 0};
arr[1] = 2;
}
public static void main(String[] args) {
new Thread(() -> instance = new Data()).start();
System.out.println(Arrays.toString(instance.arr));
}
}不正确
public static class Data {
final int[] arr;
public Data() {
arr = new int[]{1, 0};
arr[1] = 2;
}
}假设一个线程创建这个类的一个对象,而另一个对这个对象具有引用的线程从数组arr读取值。第二个线程是否可以观察数组1, 0的arr值?
为了检查这个例子,我用JCStress框架编写了测试(感谢@AlekseyShipilev):
下面的注释之后,测试似乎也不正确,也不正确
@JCStressTest
@Outcome(id = "2, 1", expect = Expect.ACCEPTABLE, desc = "Seeing the set value.")
@Outcome(expect = Expect.FORBIDDEN, desc = "Other values are forbidden.")
@State
public class FinalArrayTest {
Data data;
public static class Data {
final int[] arr;
public Data() {
arr = new int[]{1, 0};
arr[1] = 2;
}
}
@Actor
public void actor1() {
data = new Data();
}
@Actor
public void actor2(IntResult2 r) {
Data d = this.data;
if (d == null) {
// Pretend we have seen the set value
r.r1 = 2;
r.r2 = 1;
} else {
r.r1 = d.arr[1];
r.r2 = d.arr[0];
}
}
}在我的机器上,第二个线程总是观察最后一个任务arr[1] = 2,但我仍然怀疑,在所有的平台(如ARM )上,我会有相同的结果吗?
所有测试都是在计算机上执行的,配置如下:
发布于 2016-06-25 16:07:43
公理的最终字段语义是由一种特殊的发生之前的规则所控制的。这个规则是(从我的JMM语用学中滑出的,但后面的大多数解释都是由于https://stackoverflow.com/users/1261287/vladimir-sitnikov):

现在。在初始存储之后修改数组元素的示例中,下面是操作与程序的关联方式:
public class FinalArrayTest {
Data data;
public static class Data {
final int[] arr;
public Data() {
arr = new int[]{1, 0};
arr[1] = 2; // (w)
} // (F)
}
@Actor
public void actor1() {
data = new Data(); // (a)
}
@Actor
public void actor2(IntResult1 r) {
// ignore null pointers for brevity
Data d = this.data;
int[] arr = d.arr; // (r1)
r.r1 = arr[1]; // (r2)
}
}w hb F和F hb a。a mc r1 (由a mc read(data)和read(data) dr read(data.arr)所致)最后,r1 dr r2,因为它是数组元素的取消引用。构造已经完成,因此在读取操作arr[1] = 2之前就会发生写操作r.r1 = arr[1] (reads 2)。换句话说,这种执行要求在arr[1]中看到"2“。
注意:为了证明所有的执行都产生了"2",您必须证明没有任何执行可以将初始存储读取到数组元素。在这种情况下,这几乎是微不足道的:没有执行可以看到数组元素写入和绕过冻结操作。如果存在this“泄漏”,则这种执行是微不足道的构造性执行。
旁白:请注意,这意味着最终字段存储初始化顺序与最终字段保证无关,只要没有泄漏。(这就是spec在说“它还将看到那些最终字段所引用的对象或数组的版本”时所提到的内容,这些字段至少与最终字段一样最新。)
发布于 2016-06-25 14:01:31
假设一个线程创建这个类的一个对象,而另一个对这个对象具有引用的线程从数组arr读取值。
使用编写的示例,这在构造函数返回之前是不可能的。该引用不是由构造函数发布的;也就是说,它是安全发布的。
第二个线程是否可能观察数组arr的1,0值?
不是的。因为这个对象是安全发布的,所以这个JLS 17.5。保证发挥作用:
“对象在构造函数完成时被认为是完全初始化的。只有在对象被完全初始化后才能看到对对象的引用的线程,才能看到该对象的最终字段的正确初始化值。”
通过应用JLS 17.5.1的规则,我们可以看到,这些保证扩展到了构造函数体末端冻结之前由构造函数初始化的任何完全封装的对象。
在Goetz等人的"Java:在行动中的并发性“中也用更容易理解的术语描述了这一点。
如果要将示例更改为:
public static class Data {
public static Data instance;
final int[] arr;
public Data() {
instance = this;
arr = new int[]{1, 0};
arr[1] = 2;
}
}我添加的语句改变了一切。现在,其他一些线程可以在构造函数完成之前看到一个Data实例。然后,它可以看到arr[1]处于其中间状态。
在Data实例仍在构建时对它的引用的“泄漏”是不安全的发布。
https://stackoverflow.com/questions/38029187
复制相似问题