Question 6 · Section 13

Why are immutable objects thread-safe?

Immutable objects are thread-safe because a race condition requires at least one write and one read. If there are no writes after creation — a race condition is physically impos...

Language versions: English Russian Ukrainian

Basic Level

Immutable objects are thread-safe because a race condition requires at least one write and one read. If there are no writes after creation — a race condition is physically impossible.

public final class Config {
    private final String url;
    private final int timeout;

    public Config(String url, int timeout) {
        this.url = url;
        this.timeout = timeout;
    }

    public String getUrl() { return url; }
    public int getTimeout() { return timeout; }
    // No setters — no one can change the state
}

This object can be safely passed between threads — no need for synchronized or Lock.

Important: immutability ≠ thread-safe for references

An immutable object is safe for reading from many threads. But if its field references a mutable object — that object needs to be protected separately.


Intermediate Level

Absence of Race Conditions

A Race Condition occurs when threads simultaneously read and write the same data. An immutable object doesn’t allow writes — write conflicts are physically impossible.

Java Memory Model guarantees for final fields

Even if the object doesn’t change, there’s a risk of seeing it in a partially initialized state. The final keyword prevents this:

  • At the end of the constructor, a “freeze” of all final fields occurs
  • Any thread that receives a reference to the object after the constructor completes will see all final fields initialized
  • The processor and compiler cannot reorder instructions so that the reference becomes available before final fields are set

Copy-on-“modification”

public final class Counter {
    private final int value;

    public Counter(int value) { this.value = value; }

    public Counter increment() {
        return new Counter(this.value + 1); // new object
    }
}

Advanced Level

Freeze Semantics and Memory Barriers

The JLS (Java Language Specification) §17.5 guarantees: after the constructor completes, all final fields are visible to any thread. This is ensured by a memory barrier at the end of the constructor, which:

  1. Prohibits reordering of instructions beyond the barrier
  2. Guarantees visibility of all final fields
  3. Creates safe publication without volatile or synchronization

Object vs Reference

It’s important to distinguish the immutability of the object itself from the immutability of the reference to it:

public class Service {
    private ImmutableObject data = new ImmutableObject("v1"); // The reference is mutable!

    public void update(String val) {
        this.data = new ImmutableObject(val); // Not thread-safe without volatile/final
    }
}

The ImmutableObject itself is thread-safe, but the data reference is not. Other threads may see the old reference from their cache.

Usage patterns

  • Copy-On-WriteCopyOnWriteArrayList relies on array immutability
  • Value Objects in DDD — immutable objects for data transfer between layers
  • Actors / Event Sourcing — each event is immutable, state is recalculated

Summary for Advanced

  • Thread-safety = no mutation + JMM guarantees for final fields
  • Immutability eliminates synchronization overhead — lock-free access eliminates contention between threads, which under high contention can reduce throughput by 50-90%.
  • Always use final for publishing immutable objects
  • For mutable references to immutable objects, use volatile or AtomicReference

Interview Cheat Sheet

Must know:

  • Race condition impossible — no writes after object creation
  • final fields ensure safe publication — JLS §17.5, freeze at end of constructor
  • Memory barrier at the end of constructor prohibits instruction reordering
  • Immutability ≠ thread-safe for references: the reference to the object may be mutable
  • Copy-On-Write pattern — CopyOnWriteArrayList relies on array immutability
  • For mutable references: volatile or AtomicReference

Frequent follow-up questions:

  • Why is synchronized not needed? — No writes = no data competition
  • What is freeze in JMM? — Memory barrier at end of constructor, guaranteeing final field visibility
  • Can a thread see a partially created object? — Without final fields — yes; with final — no (safe publication)
  • What if the reference to an immutable object is mutable? — Need volatile or AtomicReference for safe replacement

Red flags (DO NOT say):

  • “All objects without setters are thread-safe” — mutable fields inside are still dangerous
  • “synchronized makes an object immutable” — that’s about synchronization, not immutability
  • “final fields can be changed via reflection” — in Java 16+ this is blocked for java.base
  • “Immutable object = thread-safe reference” — the reference itself may be mutable

Related topics:

  • [[1. What is an immutable object]]
  • [[2. What advantages do immutable objects provide]]
  • [[7. What is the final keyword and how does it help create immutable classes]]
  • [[23. How does immutability affect performance]]