我正在阅读一篇文章,其中涉及双重检查锁定,但我对作为示例提供的代码中更基本的失败感到惊讶.在那里声明,实例的初始化(即,在构造函数返回之前发生的实例变量的写入)可能会在对实例的引用写入共享变量(在静态字段中)之后重新排序.以下示例).
是否正确使用以下类定义Foo,一个线程执行Foo.initFoo();并执行不同的线程System.out.println(Foo.foo.a);,第二个线程可以打印0(而不是1或抛出NullPointerException)?
class Foo {
public int a = 1;
public static Foo foo;
public static void initFoo() {
foo = new Foo();
}
public static void thread1() {
initFoo(); // Executed on one thread.
}
public static void thread2() {
System.out.println(foo.a); // Executed on a different thread
}
}
Run Code Online (Sandbox Code Playgroud)
根据我对Java内存模型(以及其他语言中的内存模型)的了解,实际上我并不感到惊讶,这是可能的,但直觉投票非常强烈,因为它是不可能的(可能因为涉及对象初始化而对象初始化似乎如此在Java中神圣).
如果0没有第一个线程中的同步,是否可以"修复"此代码(即它永远不会打印)?
一个调用foo = new Foo();涉及几个可能重新排序的操作,除非你引入适当的同步来防止它:
a = 0)a = 1)如果没有正确的同步,可能会重新排序步骤3和4(请注意,步骤2必须在步骤4之前发生),尽管x86架构上的热点不太可能发生.
为了防止它你有几个解决方案,例如:
a最后的决定foo(使用同步的initAND getter).在不进入JLS#17的复杂性的情况下,您可以阅读关于类初始化的JLS#12.4.1(强调我的):
初始化代码不受限制的事实允许构造示例,其中在评估其初始化表达式之前,当它仍然具有其初始默认值时可以观察到类变量的值,但是这样的示例在实践中是罕见的.(这些示例也可以构造为例如变量初始化.)这些初始化器中可以使用Java编程语言的全部功能.程序员必须小心谨慎.这种能力给代码生成器带来了额外的负担,但是在任何情况下都会产生这种负担,因为Java编程语言是并发的.
| 归档时间: |
|
| 查看次数: |
594 次 |
| 最近记录: |