问题描述
我正在阅读B. Goetz Java并发在实践中,现在我在section 3.5
上讨论安全发布.他说:
I'm reading B. Goetz Java Concurrency In practice and now I'm at the section 3.5
about safe publication. He stated:
// Unsafe publication
public Holder holder;
public void initialize() {
holder = new Holder(42);
}
我不明白为什么可以观察到部分构造的子对象.假定构造函数Holder(int)
不允许this
逸出.因此,构造的引用只能由调用者观察.现在,作为 JLS 17.7 说:
I don't see why it is possible to observe a partially constructed subobject. Assume, that the constructor Holder(int)
does not allow this
to escape. So, the constructed reference can be observed only by the caller. Now, as JLS 17.7 stated:
线程不可能观察到部分构造的对象.
it is impossible for thread to observe a partially constructed object.
我在哪里错了?
推荐答案
这是您的逻辑失误的地方,尽管看起来似乎很合理.
That's where your logic breaks, though it seems like a perfectly reasonable thing to say.
第一件事:17.7提到的原子性仅表示,当您阅读引用时,您将看到所有先前值(从其默认值null
开始)或所有某些后续值.您将永远不会获得带有对应于值1的某些位和对应于值2的某些位的引用,这实际上将使它成为对JVM堆中随机位置的引用-太糟糕了!他们基本上是说,引用本身要么为null,要么指向内存中的有效位置."但是 的记忆是什么,那东西可能会变得很奇怪.
First things first: The atomicity that 17.7 mentions only says that when you read a reference, you'll either see all of a previous value (starting with its default value of null
) or all of some subsequent value. You'll never get a reference with some bits corresponding to value 1 and some bits corresponding to value 2, which would essentially make it a reference into a random place in the JVM heap — which would be terrible! Basically they're saying, "the reference itself will either be null, or point to a valid place in memory." But what's in that memory, that's where things can get weird.
我将假设这个简单的Holder:
I'll assume this simple Holder:
public class Holder {
int value; // NOT final!
public Holder(int value) { this.value = value; }
}
鉴于此,当您执行holder = new Holder(42)
时会发生什么?
Given that, what happens when you do holder = new Holder(42)
?
- JVM为新的Holder对象分配了一些空间,其所有字段均具有默认值(即
value = 0
) - JVM调用Holder构造函数
- JVM将
<new instance>.value
设置为传入值(42). - 构造函数完成
- JVM将
- the JVM allocates some space for the new Holder object, with default values for all its fields (ie,
value = 0
) - the JVM invokes the Holder constructor
- the JVM sets
<new instance>.value
to the incoming value (42). - the constructor completes
- the JVM sets
重新排序使生活变得艰辛(但同时也使程序运行更快!)
问题在于,另一个线程可以按任何顺序查看这些事件,因为它们之间没有同步点.这是因为构造函数没有任何特殊的同步或发生在语义之前(这只是些谎言,但稍后会更多).您可以在;请注意,关于构造函数没有任何内容.
Reordering makes life hard (but it also makes programs fast!)
The problem is that another thread can view these events in any order, since there are no synchronization points between them. That's because constructors don't have any special synchronization or happens-before semantics (that's slight lie, but more on that later). You can see the full list of "synchronized-with" actions at JLS 17.4.4; note that there's nothing there about constructors.
因此,另一个线程可能会看到这些动作按(1、3、2)顺序排列.这意味着,如果在事件1和事件3之间安排了一些其他事件(例如,如果有人将Holder.holder.value
读入本地变量),那么他们将看到该新分配的对象,但在构造函数运行之前具有其值:请参见Holder.holder.value == 0
.这称为部分构造的对象,可能会造成混乱.
So, another thread might see those actions ordered as (1, 3, 2). This means that if some other event is ordered between events 1 and 3 — for instance, if someone reads Holder.holder.value
into a local var — then they'll see that newly allocated object, but with its values before the constructor has run: you'd see Holder.holder.value == 0
. This is called a partially constructed object, and it can be pretty confusing.
如果构造函数具有多个步骤(设置多个字段,或者先设置然后更改一个字段),那么您可以看到这些步骤的任何顺序.几乎所有赌注都没有了. kes!
If the constructor had multiple steps (setting multiple fields, or setting and then changing a field), then you can see any ordering of those steps. Pretty much all bets are off. Yikes!
我在上面提到,当我断言构造函数没有任何特殊的同步语义时,我就撒谎了.假设您没有泄漏this
,则有一个例外:保证所有final
字段 都被视为在构造函数的末尾(请参阅).
I mentioned above that I lied when I asserted that constructors don't have any special synchronization semantics. Assuming you don't leak this
, there's one exception to that: any final
fields are guaranteed to be seen as they were at the end of the constructor (see JLS 17.5).
您可以认为它是步骤2和步骤3之间存在一种同步点,但它仅 适用于final
字段.
You can think of it as there being a kind of synchronization point between steps 2 and 3, but it only applies to final
fields.
- 它不适用于非最终字段
- 它不适用于其他同步点.
- 它确实确实,但是可以扩展到您通过
final
字段访问的任何状态.因此,如果您有一个final List<String>
,并且您的构造函数对其进行了初始化,然后添加了一些值,那么可以保证所有线程都能看到该列表,至少具有其在构造函数末尾所处的状态,包括那些add
电话. (如果您在构造函数之后修改列表,而没有同步,则所有赌注将再次关闭.)
- It doesn't apply to non-final fields
- It doesn't apply transitively to other synchronization points.
- It does, however, extend to any state that you access through the
final
fields. So, if you have afinal List<String>
, and your constructor initializes it and then adds some values, then all threads are guaranteed to see that list with at least the state that it had at the end of the constructor, including thoseadd
calls. (If you modify the list after the constructor, without synchronization, then all bets are off again.)
这就是为什么在我上面的示例中,重要的是value
不是最终的.如果是这样,那么您将看不到Holder.holder.value == 0
.
That's why it was important in my example above that value
was not final. If it had been, then you wouldn't be able to see Holder.holder.value == 0
.
这篇关于同时访问公共领域.为什么可以观察到不一致的状态?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!