【发布时间】:2016-04-02 01:16:17
【问题描述】:
我目前正在阅读Java Concurrency in Practice by Brian Goetz。在第 51 页。在其中一个脚注中,他说:
虽然在构造函数中设置的字段值似乎是第一个 写入这些字段的值,因此没有“旧” 值视为陈旧的值,Object 构造函数首先写入 在子类构造函数运行之前所有字段的默认值。它是 因此可以将字段的默认值视为陈旧的 价值。
所以,我现在还不清楚最终字段的概念。考虑示例类:
public class MyClass{
private final MyImmutableClass mic;
public MyClass(){
mic = MyImmutableClass.empty();
}
}
根据上面的脚注,mic 字段被分配了两次,一次由Object 的构造函数分配,一次由MyClass 分配构造函数本身。现在,假设我们不安全地发布了一个MyClass 对象(例如通过public 字段):
public final MyClass mc;
谁保证mc 总是被任何处于一致状态的线程观察到?为什么有些线程不能意外观察到默认值?
据我所知,final 字段本身仅保证在对象构造后无法分配引用。如果我们声明mc volatile,那就很清楚了。任何读取该字段的线程都应该直接从内存中读取它。禁止从缓存中读取。
UPD:发布示例:
public static void main(String[] args){
class MyRunnable implements Runnable(){
private SomeClass sc;
public MyRunnable(SomeClass sc){
this.sc = sc;
}
public void run(){
//do some with sc
}
}
SomeClass sc = getInitialized();
ExecutorService es = Executors.newFixedThreadPool(10);
MyRunnable mr = new MyRunnable(sc);
//submiting mr to es 10 times
es.awaitTemination();
es.shutdown();
}
private static SomeClass getInitialized(){
SomeClass sc = new SomeClass();
sc. initialize();
return sc;
}
public class SomeClass
public MyClass mc;
public void initialize(){
mc = new MyClass();
}
}
SomeClass 实例将跨多个线程发布。某些线程可以观察mic字段的默认值吗?
【问题讨论】:
-
技术上,Brian Goetz 是不正确的,他说“对象构造函数首先将默认值写入所有字段”。 JLS section 12.5 描述了如何创建新实例,并且“新对象中的所有实例变量,包括在超类中声明的变量,都被初始化为其默认值(第 4.12.5 节)”发生在 之前构造函数,包括超类
java.lang.Object的构造函数,被调用。 -
再一次,这并没有什么不同,因为在写入默认值之后要调用的下一个东西是
java.lang.Object构造函数 -
@ErwinBolwidt 很棒的评论,非常有趣。非常感谢!!
-
我应该限定它。尽管 12.5 并不暗示发生之前的关系,但由于 17.4.4,在任何情况下都有一个:“将默认值(零、假或空)写入每个变量与每个变量中的第一个操作同步线。”并且由于在线程上调用构造函数,并且 x 与 y 同步意味着 x 发生在 y 之前,这也意味着默认变量的分配和 java.lang.Object 超类的调用之间存在发生之前的关系构造函数。
-
我花了一些时间阅读java规范,现在我相信它也会首先初始化为默认值,然后执行初始化程序。
标签: java multithreading final