问题描述
Java内存模型保证对象的构造和终结器之间发生先发生后关系:
The Java memory model guarantees a happens-before relationship between an object's construction and finalizer:
以及构造函数和final字段的初始化:
As well as the constructor and the initialization of final fields:
对于volatile
字段也有保证,因为关于对这些字段的所有访问都存在事前发生的关系:
There's also a guarantee about volatile
fields since, there's a happens-before relations with regard to all access to such fields:
但是,常规的,良好的旧非易失性字段呢?我已经看到了很多多线程代码,这些对象在使用非易失性字段构造对象之后不会麻烦创建任何类型的内存屏障.但是由于这个原因,我从未见过或听说过任何问题,而且我自己也无法重新创建这种局部构造.
But what about regular, good old non-volatile fields? I've seen a lot of multi-threaded code that doesn't bother creating any sort of memory barrier after object construction with non-volatile fields. But I've never seen or heard of any issues because of it and I wasn't able to recreate such partial construction myself.
现代JVM是否在构造后就设置了内存屏障?避免在施工前后重新排序?还是我只是幸运?如果是后者,是否可以编写可随意复制部分构造的代码?
Do modern JVMs just put memory barriers after construction? Avoid reordering around construction? Or was I just lucky? If it's the latter, is it possible to write code that reproduces partial construction at will?
为澄清起见,我正在谈论以下情况.假设我们有一堂课:
To clarify, I'm talking about the following situation. Say we have a class:
public class Foo{
public int bar = 0;
public Foo(){
this.bar = 5;
}
...
}
一些线程T1
实例化了一个新的Foo
实例:
And some Thread T1
instantiates a new Foo
instance:
Foo myFoo = new Foo();
然后将实例传递给其他线程,我们将其称为T2
:
Then passes the instance to some other thread, which we'll call T2
:
Thread t = new Thread(() -> {
if (myFoo.bar == 5){
....
}
});
t.start();
T1
执行了两次有趣的写操作:
T1
performed two writes that are interesting to us:
- T1将值5写入新实例化的
myFoo
的 - T1将对新创建对象的引用写到
myFoo
变量
bar
- T1 wrote the value 5 to
bar
of the newly instantiatedmyFoo
- T1 wrote the reference to the newly created object to the
myFoo
variable
对于T1,我们得到一个保证写入#1 发生之前写入#2:
For T1, we get a guarantee that write #1 happened-before write #2:
但是就T2
而言,Java内存模型不提供这种保证.没有什么可以阻止它以相反的顺序看到写入.因此它可以看到一个完全构建的Foo
对象,但是bar
字段等于0.
But as far as T2
is concerned the Java memory model offers no such guarantee. Nothing prevents it from seeing the writes in the opposite order. So it could see a fully built Foo
object, but with the bar
field equal to equal to 0.
Edit2
写完几个月后,我再次看了一下上面的例子.自从T1
写入后启动T2
以来,实际上可以保证该代码正常工作.这使它成为我要提出的问题的错误示例.修复了假设T1
在执行写操作时T2已经在运行的问题.假设T2
正在循环读取myFoo
,就像这样:
I took a second look at the example above a few months after writing it. And that code is actually guaranteed to work correctly since T2
was started after T1
's writes. That makes it an incorrect example for the question I wanted to ask. The fix it to assume that T2 is already running when T1
is performing the write. Say T2
is reading myFoo
in a loop, like so:
Foo myFoo = null;
Thread t2 = new Thread(() -> {
for (;;) {
if (myFoo != null && myFoo.bar == 5){
...
}
...
}
});
t2.start();
myFoo = new Foo(); //The creation of Foo happens after t2 is already running
推荐答案
以您的示例作为问题本身-答案是是,这是完全可能的.就像您引用的那样,初始化的字段仅对构造线程可见( ).这称为安全出版物(但我敢打赌,您已经对此有所了解).
Taking your example as the question itself - the answer would be yes, that is entirely possible. The initialized fields are visible only to the constructing thread, like you quoted. This is called safe publication (but I bet you already knew about this).
您没有通过实验看到的事实是x86上的AFAIK(是一种强大的内存模型),存储仍然不重新排序,因此,除非JIT会对这些存储重新排序T1做到的-您看不到.但这真是火上浇油,这问题及其后续操作(几乎相同) 1200万设备
JLS仅保证实现可见性的几种方法.而且,绕过btw并非如此,JLS不会说何时会中断,而会说何时会起作用.
请注意示例如何显示每个字段必须为final
-即使在当前实现中,一个单个就足够了,并且存在两个内存障碍在构造函数:LoadStore
和StoreStore
之后插入(使用final时).
Notice how the example shows that each field has to be final
- even if under the current implementation a single one would suffice, and there are two memory barriers inserted (when final(s) are used) after the constructor: LoadStore
and StoreStore
.
2)易失字段(并隐式为AtomicXXX
);我认为这个不需要任何解释,似乎您引用了这个.
2) volatile fields (and implicitly AtomicXXX
); I think this one does not need any explanations and it seems you quoted this.
3)静态初始化程序好吧,应该是显而易见的IMO
3) Static initializers well, kind of should be obvious IMO
4)一些锁定-规则发生之前,这也应该很明显...
4) Some locking involved - this should be obvious too, happens-before rule...
这篇关于在实践中,对象构造是否可以保证所有线程都看到初始化的非最终字段?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!