Concurrency and immutabilityhard8+ years

One thread repeatedly does `m.lo = i; m.hi = i;` on a shared mutable object; a reader thread checks whether it ever observes `lo != hi`. A measurement shows 4,090 torn reads with no locks anywhere. The same measurement with an immutable `Range` object — `shared = new Range(i, i);` swapped in by one reference write — shows zero. Why does the immutable version need no lock at all, and what could silently take that guarantee away?

A mutable object can be observed between its updates — the reader thread can see lo after it's been set to the new value but hi still holding the old one, because the two writes are genuinely two separate operations with a window between them the reader can land in, and nothing about ordinary field writes prevents that. An immutable Range removes the window structurally, not by luck: it's built completely, all at once, before anyone else can see it, and publishing a new one to other threads is a single reference write — there is no partially-built state anyone can observe, because there's no "between" the two field writes for a reader to land in; there's only one write, swapping the whole reference. The Java Language Specification (§17.5) makes this a real, structural guarantee rather than just good practice: for an object whose fields are all final and whose constructor never lets this escape before it finishes, every thread that later gets a reference to that object is guaranteed to see every final field's fully-initialized value, with zero synchronization needed. The one way to lose it: let this escape the constructor early — passing it to a listener, starting a thread with it, registering a callback — before construction finishes, which can let another thread observe the object before the guarantee's "freeze" actually takes effect.

The lesson behind it →