Java 为什么发布最终字段是安全的?
我现在正在读书。第51页。在其中一个脚注中,他说: 虽然在构造函数中设置的字段值似乎是第一个 写入这些字段的值,因此不存在“旧的” 要将值视为过时值,对象构造函数首先写入 在运行子类构造函数之前,所有字段的默认值。它是 因此,可以将字段的默认值视为过时值 价值观 所以,我现在还不清楚最终字段的概念。考虑样本类:Java 为什么发布最终字段是安全的?,java,multithreading,final,Java,Multithreading,Final,我现在正在读书。第51页。在其中一个脚注中,他说: 虽然在构造函数中设置的字段值似乎是第一个 写入这些字段的值,因此不存在“旧的” 要将值视为过时值,对象构造函数首先写入 在运行子类构造函数之前,所有字段的默认值。它是 因此,可以将字段的默认值视为过时值 价值观 所以,我现在还不清楚最终字段的概念。考虑样本类: public class MyClass{ private final MyImmutableClass mic; public MyClass(){
public class MyClass{
private final MyImmutableClass mic;
public MyClass(){
mic = MyImmutableClass.empty();
}
}
根据上述脚注,mic
字段被赋值两次,一次由对象的构造函数赋值,一次由MyClass的构造函数本身赋值。现在,假设我们不安全地发布了一个MyClass
对象(例如,通过public
字段):
谁能保证任何线程始终在一致状态下观察到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
字段的默认值?mc
在您的示例中是一个实例变量。这意味着您必须拥有一个包含mc
的类的完全初始化实例,以便访问某个实例的mc
的任何代码都不会抛出NullPointerException
。因此,mc
在被访问时肯定会被初始化
…对象构造函数首先将默认值写入所有
子类构造函数运行之前的字段
对象
类构造函数无法看到只属于MyClass
的MyClass
成员(不是从对象
继承的)。因此,上述语句是正确的,Object
类无法实例化成员变量mic
…根据上述脚注,麦克风场分配两次,
一次由对象的构造函数执行,一次由MyClass的构造函数执行
本身
否。对象
构造函数仅初始化其成员变量。然后MyClass
构造函数将初始化它的mic
。最后,您将拥有MyClass
实例。因此,mic
不会被分配两次,即使mic
不是最终的
发布示例:代码段未完成。但是,跨多个线程访问某个对象取决于许多因素,例如它是否是静态成员?、父对象是否在某处被引用为静态成员?、mc在何时何地初始化?(默认值null
通过构造函数确定)。如果不是静态成员,它应该是单例,等等。所以,如果我们以前没有访问过变量,就没有线程本地缓存,所以我们必须直接从内存中读取它。正确吗?@St.Antario我不确定我是否理解你的评论。在您的示例中,哪个代码将访问mc
?你能展示一些我可以评论的代码片段吗?我添加了发布的示例。@St.Antario你没有在新代码片段中显示调用initialize
,我真的不明白你的意思。他说,对象的构造函数将默认值写入所有字段……他说的是正确的。你对super()构造函数的调用感到困惑……啊,你的意思是,只要调用super()就可以让派生类的所有字段都保留默认值,对吗?从技术上讲,Brian Goetz说“对象构造函数首先将默认值写入所有字段”是错误的。描述如何创建新实例,以及在调用任何构造函数(包括超类java.lang.object
的构造函数)之前,“新对象中的所有实例变量,包括在超类中声明的实例变量,均初始化为其默认值(§4.12.5)”。然后,这没有什么区别,因为在编写默认值之后,接下来要调用的就是java.lang.Object
constructor@ErwinBolwidt评论不错,很有意思。多谢!!我应该对此加以限定。虽然12.5并不意味着一个先发生后发生的关系,但在任何情况下,由于17.4.4,“将默认值(零、假或空)写入每个变量都与每个线程中的第一个操作同步。”并且由于构造函数是在线程上调用的,而x与y同步意味着x发生在y之前,这也意味着在默认变量的赋值和java.lang.Object超类构造函数的调用之间存在一种先发生后发生的关系。我花了一些时间阅读java规范,现在我相信它也将首先初始化为默认值,然后执行初始化器。
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();
}
}