Reputation: 821
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?
Upvotes: 2
Views: 669
Reputation: 19682
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
.
Upvotes: 6
Reputation: 77226
No, an immutable object can still be unsafely published if the constructor leaks a reference to this
before returning (which is where the happens-before kicks in).
The two likely routes for the reference leak are if the constructor attempts to register the new object for callbacks (such as as an event listener on some constructor parameter) or with a registry or, more subtly, calls a non-final method that is overridden to do the same thing.
Upvotes: 2