不可变对象是否对不正当的出版物免疫? [英] Are Immutable objects immune to improper publication?

查看:147
本文介绍了不可变对象是否对不正当的出版物免疫?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

这是 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 有关;对 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屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆