Safe publication means making an object visible to other threads in such a way that they see it fully constructed — all its fields properly initialized — rather than a partially-built or stale version. Without it, a reader can observe a non-null reference whose fields still hold default values.
Why naive publication is broken
Object construction involves multiple writes (the reference assignment and each field write). The JMM permits these to be reordered and to be invisible to other threads without a happens-before edge. So a thread that reads a shared reference might see the reference but not the writes that initialized the object's fields:
class Holder { int n; Holder() { n = 42; } }
Holder shared; // plain field
// Thread A
shared = new Holder(); // reference write may become visible
// before the n = 42 write
// Thread B
if (shared != null) use(shared.n); // could legally read n == 0
The safe ways to publish
An object is safely published when both the publication and the construction are ordered by happens-before. The standard mechanisms:
- Static initializer — fields/objects assigned in a
staticblock or static field initializer are safely published by the JVM's class-initialization lock. volatilefield (orAtomicReference) — a volatile write happens-before the matching volatile read.finalfield — final fields are frozen at the end of the constructor; any thread that reads the reference (published normally afterward) sees the correct final-field values. This is what makes immutable objects safe.- A lock — writing inside
synchronizedand reading insidesynchronizedon the same monitor. - A concurrent / synchronized collection — putting an object into a
ConcurrentHashMap,BlockingQueue, etc. safely publishes it to whoever takes it out.
The this-escape problem
If this becomes visible to another thread before the constructor finishes, that thread can see a half-built object — and even final-field guarantees don't apply, because the freeze happens at the end of construction. Common ways this escapes:
class Listener {
Listener(EventSource src) {
// BAD: 'this' escapes before construction completes
src.register(this); // another thread may now call us
this.handler = ...; // ...before this runs
}
}
Starting a thread from the constructor, registering a callback/listener, or passing this to any external code mid-construction all leak a not-yet-final object. The fix is a factory method: construct fully, then publish (e.g. register) afterward.