为什么发布最终字段是安全的?
Why is publishing final fields safe?
我正在阅读 Java Concurrency in Practice by Brian Goetz。在第 51 页。在其中一个脚注中,他说:
While it may seem that field values set in a constructor are the first
values written to those fields and therefore that there are no “older”
values to see as stale values, the Object constructor first writes the
default values to all fields before subclass constructors run. It is
therefore possible to see the default value for a field as a stale
value.
所以,final 字段的概念现在对我来说还不是很清楚。考虑示例 class:
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
字段的默认值吗?
mc
在你的例子中是一个实例变量。这意味着您必须具有包含 mc
的 class 的完全初始化实例,以便任何访问某个实例的 mc
的代码都不会抛出 NullPointerException
。所以mc
访问的时候肯定会初始化
...the Object constructor first writes the default values to all
fields before subclass constructors run...
Object
class 构造函数看不到 MyClass
只属于 MyClass
的成员(不是从 Object
继承的)。所以上面的说法是正确的,Object
class不能实例化成员变量mic
.
...According to the above footnote, the mic field is assigned twice,
once by the Object's constructor and once by the MyClass's constructor
itself...
没有。 Object
构造函数只初始化它的成员变量。然后 MyClass
构造函数将初始化它的 mic
。最后,您将拥有 MyClass
实例。因此,即使 mic
是非最终的 mic
也不会被分配两次。
发布示例:代码片段未完成。但是,跨多个线程访问某些内容取决于许多因素,例如 它是否是静态成员?、父对象是否在某处被引用为静态成员? , mc
何时何地初始化?(默认值 null
肯定是通过构造函数)。 如果不是静态成员,应该是单例,等等...
我正在阅读 Java Concurrency in Practice by Brian Goetz。在第 51 页。在其中一个脚注中,他说:
While it may seem that field values set in a constructor are the first values written to those fields and therefore that there are no “older” values to see as stale values, the Object constructor first writes the default values to all fields before subclass constructors run. It is therefore possible to see the default value for a field as a stale value.
所以,final 字段的概念现在对我来说还不是很清楚。考虑示例 class:
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
字段的默认值吗?
mc
在你的例子中是一个实例变量。这意味着您必须具有包含 mc
的 class 的完全初始化实例,以便任何访问某个实例的 mc
的代码都不会抛出 NullPointerException
。所以mc
访问的时候肯定会初始化
...the Object constructor first writes the default values to all fields before subclass constructors run...
Object
class 构造函数看不到 MyClass
只属于 MyClass
的成员(不是从 Object
继承的)。所以上面的说法是正确的,Object
class不能实例化成员变量mic
.
...According to the above footnote, the mic field is assigned twice, once by the Object's constructor and once by the MyClass's constructor itself...
没有。 Object
构造函数只初始化它的成员变量。然后 MyClass
构造函数将初始化它的 mic
。最后,您将拥有 MyClass
实例。因此,即使 mic
是非最终的 mic
也不会被分配两次。
发布示例:代码片段未完成。但是,跨多个线程访问某些内容取决于许多因素,例如 它是否是静态成员?、父对象是否在某处被引用为静态成员? , mc
何时何地初始化?(默认值 null
肯定是通过构造函数)。 如果不是静态成员,应该是单例,等等...