On Wed, May 7, 2014 at 6:31 AM, Alex Miller <a...@puredanger.com> wrote:
It does matter with regard to visibility across threads - your example does
> not use a synchronization mechanism and there is no guarantee that other
> threads will ever see those changes (so don't ever ever do that :). But if
> you stick to the normal Clojure apis, all is good. I'd highly recommend
> reading JCIP to dive into the details.
>
> Final field freeze is particularly weird and it baked my noodle when I
> first encountered it - here's a blog I wrote about it approx 697 years ago
> in internet time (and Brian Goetz backs me up in the comments :)
> http://tech.puredanger.com/2008/11/26/jmm-and-final-field-freeze/
>
 I believe that while JCIP is useful, it is also confusing, because it is
trying to explain Java’s memory model in layman’s terms. I recently
experienced “enlightenment”, by making an effort to understand how things
work at a lower level.

Happens-before relationships / ordering guarantees are given by the
compiler, in combination with the CPU, with the rules being implied by
usage of memory fences (barriers). Without ordering guarantees, several
things can happen - the compiler could optimize the code by reordering or
eliminating instructions, the CPU might optimize the code by reordering or
eliminating instructions and - what bits beginners the most - today’s
multi-core CPUs are tricky, as each CPU has a store buffer that’s local to
each core (i.e. other cores cannot see it) and published values only become
visible to other cores when the store buffer gets flushed to the L1 cache.
And you have no guarantee that this flush will *ever* happen, or even after
it happens, you have no guarantee that other processors that already have a
cached value will make an effort to read the more up-to-date value … unless
ordering semantics are enforced. And actually thinking in terms of flushes
to memory is dangerous, as nothing you do guarantees a flush - everything
being relative, specified in terms of happens-before relationships, implied
by memory barriers.

And memory barriers are tricky beasts, as they come in multiple shapes and
sizes. The JSR 133 Cookbook
<http://gee.cs.oswego.edu/dl/jmm/cookbook.html>is a useful document
for shedding some light on how they work. In
particular, for final fields, you get a guarantee for a *StoreStore* memory
barrier, like so:

x.finalField = v; *StoreStore*; sharedRef = x;

This guarantee basically says that the finalFields’ value will always be
stored before the write sharedRef = x happens. Or in other words, once the
constructor of x is finished and the value stored inside a sharedRef, the
finals are already fully initialized. And this extends to whatever writes
that happened in the object stored in that final field too. This assumes
that this didn’t escape during construction - in which case the above
guarantee becomes meaningless for threads that already saw object x. As an
aside, a StoreStore fence on X86 is a no-op, as stores on X86 are ordered,
but for other processors this is not true and the compiler can and does
reorder instructions by itself.

Also consider doing something like:

 final int[] field = new int[] {1,2,3,4}

Even though that array is not immutable, the array and the values in it
will be visible once the final field itself becomes visible, again because
of the store ordering guarantee. But this doesn’t hold if that array
reference has been seen before, so this doesn’t work as people might think:

final int[] field;
public Constructor(int[] arr) { field = arr }

My initial impression was that a final field store behaves like a volatile
write. That’s not true. A volatile write is *preceded* by a StoreStore,
while a final write is succeeded by one. Also, a volatile write followed by
a volatile read guarantees a Store/Load memory barrier between the two
actions for ordering previous stores with subsequent loads - this is the
rule that says that once a volatile is stored, then subsequent reads are
ordered after that store (so the volatile read is basically monitor enter
and the volatile write is basically monitor exit … the similarity with
acquiring locks is not accidental at all) and so threads don’t get stale
data. Finals don’t get the same treatment, even though there are
similarities between the two … this is also why it is important for this to
not escape during construction or for why the array reference received from
the outside in the above won’t necessarily be up-to-date when the final
will be observed by other threads - again, ordering guarantees are relative.

In other words, for finals to work properly, this mustn’t escape during
construction and care must be taken when storing references to mutable
things received from the outside.
-- 
Alexandru Nedelcu
www.bionicspirit.com

PGP Public Key:
https://bionicspirit.com/key.aexpk

-- 
You received this message because you are subscribed to the Google
Groups "Clojure" group.
To post to this group, send email to clojure@googlegroups.com
Note that posts from new members are moderated - please be patient with your 
first post.
To unsubscribe from this group, send email to
clojure+unsubscr...@googlegroups.com
For more options, visit this group at
http://groups.google.com/group/clojure?hl=en
--- 
You received this message because you are subscribed to the Google Groups 
"Clojure" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to clojure+unsubscr...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Reply via email to