不可变对象是否对不正当的出版物免疫? [英] Are Immutable objects immune to improper publication?
问题描述
这是 JCiP 的一个例子。
public class Unsafe {
// Unsafe publication
public Holder holder;
public void initialize() {
holder = new Holder(42);
}
}
public class Holder {
private int n;
public Holder(int n) {
this.n = n;
}
public void assertSanity() {
if (n != n) {
throw new AssertionError("This statement is false.");
}
}
}
第34页:
[15]这里的问题不是Holder类本身,而是
Holder未正确发布。但是,通过宣布n字段为最终字段,Holder可以通过不正确的发布免费获得
,其中b $ b将使Holder不可变;
[15] The problem here is not the Holder class itself, but that the Holder is not properly published. However, Holder can be made immune to improper publication by declaring the n field to be final, which would make Holder immutable;
来自此答案:
final的规范(参见@ andersoj的答案)保证
当构造函数返回时,final字段将具有已正确
初始化(从所有线程可见)。
the specification for final (see @andersoj's answer) guarantees that when the constructor returns, the final field will have been properly initialized (as visible from all threads).
来自 wiki :
例如,在Java中如果对构造函数的调用已内联
,则共享变量可能会立即更新一次
已分配存储但在内联构造函数
初始化对象之前
For example, in Java if a call to a constructor has been inlined then the shared variable may immediately be updated once the storage has been allocated but before the inlined constructor initializes the object
我的问题是:
因为:(可能是错的,我不知道。)
Because : (could be wrong, I don't know.)
a)共享变量可能会在内联构造函数init之前立即更新ializes对象。
a) the shared variable may immediately be updated before the inlined constructor initializes the object.
b)只有当构造函数返回时,才能保证最终字段被正确初始化(从所有线程可见)。
b) the final field will be guaranteed to be properly initialized (as visible from all threads) ONLY when the constructor returns.
是否有可能另一个线程看到默认值 holder.n
? (即另一个线程在 holder
构造函数返回之前获得对 holder
的引用。)
Is it possible that another thread sees the default value of holder.n
? (i.e. Another thread gets a reference to holder
before the holder
constructor returns.)
如果是这样,那你如何解释下面的陈述?
If so, then how do you explain the statement below?
持有人可以免受不正当的出版物的影响声明n
字段为final,这将使Holder不可变
Holder can be made immune to improper publication by declaring the n field to be final, which would make Holder immutable
编辑:
来自JCiP。不可变对象的定义:
From JCiP. The definition of an immutable object:
如果符合以下情况,对象是不可变的:
x $ b后其状态无法修改$ b建筑;
An object is immutable if:
x Its state cannot be modified after construction;
x所有字段都是最终字段; [12]和
x All its fields are final;[12] and
x构造正确
(此引用在构造期间不会转义)。
x It is properly constructed (the this reference does not escape during construction).
因此,根据定义,不可变对象没有这个
引用转义问题。对吗?
So, by definition, immutable objects don't have "this
reference escaping" problems. Right?
但是他们会受到如果没有声明为volatile,则以双重检查锁定模式进行无序写入?
But will they suffer from Out-of-order writes in double-checked-locking pattern if not declared to be volatile?
推荐答案
不可变对象,例如字符串
,对于所有读者来说似乎都具有相同的状态,无论其参考是如何获得的,即使同步不当和缺乏事先关系也是如此。
An immutable object, e.g. String
, appears to have the same state for all readers, regardless how its reference is obtained, even with improper synchronization and lack of happens-before relationship.
这是通过Java 5中引入的 final
字段语义实现的。通过final字段的数据访问具有更强的内存语义,如< a href =https://docs.oracle.com/javase/specs/jls/se8/html/jls-17.html#jls-17.5.1\"rel =nofollow> jls-17.5.1
This is achieved by final
field semantics introduced in Java 5. Data access through a final field has a stronger memory semantics, as defined in jls-17.5.1
在编译器重新排序和内存障碍方面,处理最终字段时有更多限制,请参阅 JSR-133 Cookbook 。你担心的重新排序不会发生。
In terms of compiler reordering and memory barriers, there are more constraints when dealing with final fields, see JSR-133 Cookbook. The reordering you worried about won't happen.
是的 - 双重检查锁定可以通过包装器中的最后一个字段完成;不需要 volatile
!但这种方法不一定更快,因为需要两次读取。
And yes -- double-checked locking can be done through a final field in a wrapper; no volatile
is required! But this approach is not necessarily faster, because two reads are needed.
请注意,此语义适用于单个最终字段,而不是整个对象。例如, String
包含一个可变字段 hash
;尽管如此, String
被认为是不可变的,因为它的公共行为仅基于 final
字段。
Note that this semantics applies to individual final fields, not the entire object as a whole. For example, String
contains a mutable field hash
; nevertheless, String
is considered immutable because its public behavior are only based on final
fields.
最终字段可以指向可变对象。例如, String.value
是一个 char []
,这是可变的。要求不可变对象是最终字段的树是不切实际的。
A final field can point to a mutable object. For example, String.value
is a char[]
which is mutable. It's impractical to require that an immutable object is a tree of final fields.
final char[] value;
public String(args) {
this.value = createFrom(args);
}
只要我们不修改值
构造函数退出后,没关系。
As long as we don't modify the content of value
after constructor exit, it's fine.
我们可以修改值的内容
在任何顺序的构造函数中,都没关系。
We can modify the content of value
in the constructor in any order, it doesn't matter.
public String(args) {
this.value = new char[1];
this.value[0] = 'x'; // modify after the field is assigned.
}
另一个例子
final Map map;
List list;
public Foo()
{
map = new HashMap();
list = listOf("etc", "etc", "etc");
map.put("etc", list)
}
任何访问权限 通过 最终字段似乎是不可变的,例如 foo.map.get(etc)。get(2)
。
访问不通过最终字段不会 - foo.list.get(2)
通过不正确的发布是不安全的,即使它读取的是同一目的地。
Access not through a final field does not -- foo.list.get(2)
is not safe through improper publication, even though it reads the same destination.
这些是设计动机。现在让我们看看JLS如何在 jls-17.5.1
Those are the design motivations. Now let's see how JLS formalizes it in jls-17.5.1
一个冻结
动作是在构造函数出口处定义的,如同到最后一个领域的任务。这允许我们在构造函数内的任何地方写入以填充内部状态。
A freeze
action is defined at the constructor exit, as apposed to at the assignment of the final field. This allows us to write anywhere inside the constructor to populate internal state.
不安全发布的常见问题是缺少发生之前( hb
)关系。即使读取看到写入,它也不会对其他行为产生任何影响。但是如果易失性读取看到易失性写入,则JMM建立 hb
以及许多操作中的订单。
The usual problem of unsafe publication is the lack of happens-before (hb
) relationship. Even if a read sees a write, it establishes nothing w.r.t other actions. But if a volatile read sees a volatile write, JMM establishes hb
and an order among many actions.
final
字段语义想要做同样的事情,即使是正常的读写操作,即使是通过不安全的出版物也是如此。为此,在读取所看到的任何写入之间添加了一个内存链( mc
)顺序。
The final
field semantics wants to do the same thing, even with normal reads and writes, that is, even through unsafe publications. To do that, a memory chain (mc
) order is added between any write seen by a read.
A deferences()
订单限制语句访问到最后一个字段。
A deferences()
order limits the semantics to accesses through the final field.
让我们重新访问 Foo
示例以查看其工作原理
Let's revisit the Foo
example to see how it works
tmp = new Foo()
[w] write to list at index 2
[f] freeze at constructor exit
shared = tmp; [a] a normal write
// Another Thread
foo = shared; [r0] a normal read
if(foo!=null) // [r0] sees [a], therefore mc(a, r0)
map = foo.map; [r1] reads a final field
map.get("etc").get(2) [r2]
我们
hb(w, f), hb(f, a), mc(a, r1), and dereferences(r1, r2)
因此 w
对 r2
可见。
基本上,通过 Foo
包装器,一个地图(本身是可变的)通过不安全的发布安全发布......如果这是有意义的。
Essentially, through Foo
wrapper, a map (which is mutable in itself) is published safely though unsafe publication... if that makes sense.
我们可以使用包装器建立最终字段语义然后丢弃吗?喜欢
Can we use the wrapper to establish final field semantics then discard it? Like
Foo foo = new Foo(); // [w] [f]
shared_map = foo.map; // [a]
有趣的是,JLS包含足以排除此类用例的条款。我猜它已被削弱,因此即使使用最终字段,也允许更多的内部线程优化。
Interestingly, JLS contains enough clauses to exclude such use case. I guess it's weakened so that more inner-thread optimizations are permitted, even with final fields.
请注意,如果此冻结行动之前泄露此
,不保证最终字段语义。
Note that if this
is leaked before the freeze action, final field semantics is not guaranteed.
然而,我们可以安全地泄漏这个
in 冻结操作后的构造函数,带构造函数链接。
However, we can safely leak this
in a constructor after the freeze action, with constructor chaining.
-- class Bar
final int x;
Bar(int x, int ignore)
{
this.x = x; // assign to final
} // [f] freeze action on this.x
public Bar(int x)
{
this(x, 0);
// [f] is reached!
leak(this);
}
就 x $而言,这是安全的c $ c>有关;对
x
的冻结操作是在分配了 x
的构造函数的存在下定义的。这可能只是为了安全地泄漏这个
。
This is safe as far as x
is concerned; freeze action on x
is defined at the exist of the constructor in which x
is assigned. This was probably designed just to safely leak this
.
这篇关于不可变对象是否对不正当的出版物免疫?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!