Definition
- Unsafe publication – without proper synchronization, the JVM or CPU may reorder instructions so that a reference to a new object is visible to other threads before the constructor finishes. The reading thread sees non-null reference but null fields.
- Final fields guarantee – the JMM guarantees that all
finalfields are fully initialized and visible to any thread that obtains a reference to the object after the constructor completes (the freeze semantic). - Volatile reference – writing the object reference to a
volatilefield after construction establishes a happens-before relationship. The reading thread sees the fully constructed object. - Synchronized/thread-safe collection – publishing inside a
synchronizedblock or viaConcurrentHashMap.put()provides built-in happens-before guarantees. - Double-checked locking – the singleton pattern
if (instance == null) synchronized { if (instance == null) instance = new Singleton(); }requiresvolatileon the instance field to prevent seeing a partially constructed singleton.
Building a new bank branch (constructing an object): unsafe publication is putting up the "NOW OPEN" sign before the vault is installed and tellers are hired. Customers walk in and find an empty building. Safe publication is finishing everything first, then putting up the sign. Final fields are like the vault being permanently installed during construction: it cannot be seen half-built. volatile is the sign on a special board that guarantees everyone sees the complete branch.
Understanding the Concepts
What is unsafe publication?
Without safe publication, the JVM may reorder the constructor's field assignments and the assignment of the object reference. A reading thread can see a non-null reference to an object whose fields are still null or default.
package com.bank.core;
import java.math.BigDecimal;
public class UnsafePublication {
static Account account; // NOT volatile
static void initialize() {
// The JVM may execute in this order:
// 1. Allocate memory for Account
// 2. Assign reference to 'account' (now non-null!)
// 3. Run constructor (set fields)
// Another thread reads 'account' between step 2 and 3: fields are null!
account = new Account("SAV2001", new BigDecimal("100000"));
}
}
class Account {
String id;
BigDecimal balance;
Account(String id, BigDecimal balance) {
this.id = id;
this.balance = balance;
}
}
How do final fields fix this?
The JMM guarantees that all final fields are visible after the constructor completes. This is the freeze semantic.
package com.bank.model;
import java.math.BigDecimal;
public class SafeAccount {
private final String id; // final: guaranteed visible
private final BigDecimal balance; // final: guaranteed visible
public SafeAccount(String id, BigDecimal balance) {
this.id = id;
this.balance = balance;
}
public String getId() { return id; }
public BigDecimal getBalance() { return balance; }
}
// Any thread that obtains a reference to a SafeAccount after construction
// is guaranteed to see the final fields fully initialized.
Java records are ideal for safe publication because all record fields are implicitly final.
How does a volatile reference fix this?
package com.bank.core;
import java.math.BigDecimal;
public class VolatilePublication {
static volatile Account account; // volatile!
static void initialize() {
Account temp = new Account("SAV2001", new BigDecimal("100000"));
account = temp; // volatile write: happens-before
// Any thread reading 'account' after this volatile write
// sees the fully constructed object.
}
}
How does double-checked locking require volatile?
package com.bank.service;
public class ConnectionPool {
private static volatile ConnectionPool instance; // MUST be volatile
private ConnectionPool() {
// expensive initialization
}
public static ConnectionPool getInstance() {
if (instance == null) { // first check (no lock)
synchronized (ConnectionPool.class) {
if (instance == null) { // second check (with lock)
instance = new ConnectionPool(); // volatile write
}
}
}
return instance;
}
}
// Without volatile: another thread may see non-null 'instance'
// with uninitialized fields (instruction reordering).
How do thread-safe collections provide safe publication?
ConcurrentHashMap.put() has built-in happens-before guarantees.
package com.bank.core;
import java.math.BigDecimal;
import java.util.concurrent.ConcurrentHashMap;
public class CollectionPublication {
static final ConcurrentHashMap<String, Account> cache = new ConcurrentHashMap<>();
static void publish() {
cache.put("SAV2001", new Account("SAV2001", new BigDecimal("100000")));
// CHM.put() has happens-before: readers see fully constructed Account
}
}
What are the safe publication techniques summary?
| Technique | How | When to use |
|---|---|---|
| Final fields | JMM freeze semantic | Immutable objects, records |
| Volatile reference | Happens-before on volatile write | Mutable objects, singletons |
| Synchronized block | Monitor exit happens-before monitor enter | Complex initialization |
| Thread-safe collection | Built-in happens-before | Cache loading |
| Static initializer | Class loading is thread-safe | Eager singletons |
What should a senior engineer know?
- Leaking this in constructor: if the constructor passes
thisto another thread before construction completes, that thread may see a partially constructed object. Never leakthisfrom a constructor. - Enum singletons:
enum Singleton { INSTANCE; }is safely published by the JVM's class loading mechanism. It is the simplest and safest singleton pattern. - Immutable objects are always safe: an object with all final fields and no mutable state is safely published once any thread obtains a reference to it, even without volatile.
Visual Overview
Unsafe Publication (instruction reordering):
Thread-1 (writer) Thread-2 (reader)
───────────────── ─────────────────
1. allocate memory
2. assign reference ────▶ reads reference (non-null!)
3. run constructor reads fields (NULL! constructor not done!)
Safe Publication (volatile):
Thread-1 (writer) Thread-2 (reader)
───────────────── ─────────────────
1. allocate memory
2. run constructor
3. volatile write ──────▶ volatile read
(memory barrier) (sees fully constructed object)
Key Takeaway
Safe publication ensures that other threads see a fully constructed object, not a partially initialized one with null fields. Without it, the JVM or CPU may reorder instructions so that the reference is published before the constructor finishes. The reading thread sees non-null but finds null fields.
Four safe publication techniques: final fields (JMM freeze guarantee), volatile reference (happens-before on write), synchronized block (monitor exit happens-before), and thread-safe collections (built-in happens-before). Java records are ideal because all fields are implicitly final. Double-checked locking for singletons requires volatile to prevent the partially constructed instance bug.
Remember one sentence: publish object references only after full construction, using final fields, volatile references, or synchronized blocks to establish happens-before relationships.
---