Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
368 views
in Technique[技术] by (71.8m points)

java - Are Immutable objects immune to improper publication?

It is an example from 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.");
        }
    }
}

On page 34:

[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;

And from this answer:

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).

From wiki:

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

My question is:

Because : (could be wrong, I don't know.)

a) the shared variable may immediately be updated before the inlined constructor initializes the object.

b) the final field will be guaranteed to be properly initialized (as visible from all threads) ONLY when the constructor returns.

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?

Holder can be made immune to improper publication by declaring the n field to be final, which would make Holder immutable

EDIT: From JCiP. The definition of an immutable object:

An object is immutable if:
x Its state cannot be modified after construction;

x All its fields are final;[12] and

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?

But will they suffer from Out-of-order writes in double-checked-locking pattern if not declared to be volatile?

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Answer

0 votes
by (71.8m points)

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.

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

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.

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.


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.

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.
}

Another example

final Map map;
List list;

public Foo()
{
    map = new HashMap();
    list = listOf("etc", "etc", "etc");
    map.put("etc", list)
}

Any access through the final field will appear to be immutable, e.g. foo.map.get("etc").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.


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.

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.

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() order limits the semantics to accesses through the final field.

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]

We have

hb(w, f), hb(f, a), mc(a, r1), and dereferences(r1, r2)

therefore w is visible to r2.


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]

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.

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); 
}

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.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...