java - Are Immutable objects immune to improper publication? -
it example 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 false."); } } }
on page 34:
[15] problem here not holder class itself, holder not published. however, holder can made immune improper publication declaring n field final, make holder immutable;
and this answer:
the specification final (see @andersoj's answer) guarantees when constructor returns, final field have been initialized (as visible threads).
from wiki:
for example, in java if call constructor has been inlined shared variable may updated once storage has been allocated before inlined constructor initializes object
my question is:
because : (could wrong, don't know.)
a) shared variable may updated before inlined constructor initializes object.
b) final field guaranteed initialized (as visible threads) when constructor returns.
is possible thread sees default value of holder.n
? (i.e. thread gets reference holder
before holder
constructor returns.)
if so, how explain statement below?
holder can made immune improper publication declaring n field final, make holder immutable
edit: jcip. definition of immutable object:
an object immutable if:
x state cannot modified after construction;x fields final;[12] and
x constructed (the reference not escape during construction).
so, definition, immutable objects don't have "this
reference escaping" problems. right?
but suffer out-of-order writes in double-checked-locking pattern if not declared volatile?
an immutable object, e.g. string
, appears have same state readers, regardless how reference obtained, improper synchronization , lack of happens-before relationship.
this achieved final
field semantics introduced in java 5. data access through final field has stronger memory semantics, defined in jls-17.5.1
in terms of compiler reordering , memory barriers, there more constraints when dealing final fields, see jsr-133 cookbook. reordering worried won't happen.
and yes -- double-checked locking can done through final field in wrapper; no volatile
required! approach not faster, because 2 reads needed.
note semantics applies individual final fields, not entire object whole. example, string
contains mutable field hash
; nevertheless, string
considered immutable because public behavior based on final
fields.
a final field can point mutable object. example, string.value
char[]
mutable. it's impractical require immutable object tree of final fields.
final char[] value; public string(args) { this.value = createfrom(args); }
as long don't modify content of value
after constructor exit, it's fine.
we can modify content of value
in constructor in order, doesn't matter.
public string(args) { this.value = new char[1]; this.value[0] = 'x'; // modify after field 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 final field appear immutable, e.g. foo.map.get("etc").get(2)
.
access not through final field not -- foo.list.get(2)
not safe through improper publication, though reads same destination.
those design motivations. let's see how jls formalizes in jls-17.5.1
a freeze
action defined @ constructor exit, apposed @ assignment of final field. allows write anywhere inside constructor populate internal state.
the usual problem of unsafe publication lack of happens-before (hb
) relationship. if read sees write, establishes nothing w.r.t other actions. if volatile read sees volatile write, jmm establishes hb
, order among many actions.
the final
field semantics wants same thing, normal reads , writes, is, through unsafe publications. that, memory chain (mc
) order added between write seen read.
a deferences()
order limits semantics accesses through final field.
let's revisit foo
example see how works
tmp = new foo() [w] write list @ index 2 [f] freeze @ constructor exit shared = tmp; [a] normal write // thread foo = shared; [r0] normal read if(foo!=null) // [r0] sees [a], therefore mc(a, r0) map = foo.map; [r1] reads final field map.get("etc").get(2) [r2]
we have
hb(w, f), hb(f, a), mc(a, r1), , dereferences(r1, r2)
therefore w
visible r2
.
essentially, through foo
wrapper, map (which mutable in itself) published safely though unsafe publication... if makes sense.
can use wrapper establish final field semantics discard it? like
foo foo = new foo(); // [w] [f] shared_map = foo.map; // [a]
interestingly, jls contains enough clauses exclude such use case. guess it's weakened more inner-thread optimizations permitted, final fields.
note if this
leaked before freeze action, final field semantics not guaranteed.
however, can safely leak this
in constructor after freeze action, constructor chaining.
-- class bar final int x; bar(int x, int ignore) { this.x = x; // assign final } // [f] freeze action on this.x public bar(int x) { this(x, 0); // [f] reached! leak(this); }
this safe far x
concerned; freeze action on x
defined @ exist of constructor in x
assigned. designed safely leak this
.
Comments
Post a Comment