Chaturmind
LearnDSASystem DesignInterview PrepDevOpsEngineering GrowthBlog
Start learning
Chaturmind

Structured learning paths for engineers who want to go deep. Written by practitioners.

Learn

  • Java
  • DSA
  • System Design
  • Spring Boot
  • AI / ML
  • DevOps
  • Engineering Growth
  • Java Interview Prep

Company

  • Blog
  • Contact

Legal

  • Privacy Policy
  • Terms of Service

© 2026 Chaturmind. All rights reserved.

Built for engineers who want to go deep.


← Java Interview Prep: 8+ Years (Senior & Lead)

Expert Core Java

  • Tricky Java Output, Operators & OOP Edge Cases — Interview Questions
  • Tricky Exceptions, Memory & Keyword Questions — Interview Questions
  • Classic Java Language Questions, Senior-Grade Answers — Interview Questions
  • Classic Collections, Threads & JDK APIs, Senior-Grade Answers — Interview Questions
  • Reflection, Dynamic Proxies, final & Modern OOP Design — Interview Questions

JVM Internals & Performance

  • Class Loading, Bytecode & Object Layout — Interview Questions
  • JIT Compilation & Runtime Optimisations — Interview Questions
  • Garbage Collectors Deep Dive — Interview Questions
  • JVM Tuning, GC Logs & Memory Footprint — Interview Questions
  • Memory Leaks, OutOfMemoryErrors & Profiling Tools — Interview Questions
  • Modules, Agents & Advanced JVM APIs — Interview Questions

Collections & Concurrency at Scale

  • Collections Internals & Complexity — Interview Questions
  • Iterators, Comparators & Ordering Contracts — Interview Questions
  • Concurrent Collections, Queues & Lock-Free Structures — Interview Questions
  • Threads, Executors & ForkJoin Internals — Interview Questions
  • Locks, Atomics, CAS & Synchronizers — Interview Questions
  • Java Memory Model, volatile, Fences & ThreadLocal — Interview Questions
  • Deadlock, Livelock, Starvation & Concurrent Design — Interview Questions
  • CompletableFuture, Parallel Streams & Non-Blocking I/O — Interview Questions

Modern Java (8 to 21+)

  • Lambdas & Functional Interfaces Internals — Interview Questions
  • Streams & Collectors Deep Dive — Interview Questions
  • Optional & Interface Default/Static Methods — Interview Questions
  • Java 9–25 Features & Virtual Threads — Interview Questions

Design Patterns, SOLID & Clean Code

  • Design Pattern Trade-offs & Combinations — Interview Questions
  • SOLID, Clean Code & Anti-Patterns — Interview Questions

Spring & Spring Boot Internals

  • IoC, Dependency Injection & Bean Lifecycle Internals — Interview Questions
  • Spring AOP, Proxies & @Async Internals — Interview Questions
  • Spring Configuration, Auto-Configuration & Custom Starters — Interview Questions
  • Spring MVC & REST Internals, Exception Frameworks — Interview Questions
  • Spring Security Advanced Internals — Interview Questions
  • Spring WebFlux, Reactor & R2DBC — Interview Questions
  • Spring Cloud, Observability & Distributed Tracing — Interview Questions
  • Spring Boot 3, Native Images & Production Scenarios — Interview Questions

JPA, Hibernate & Databases at Scale

  • Spring Data JPA — Queries, Projections, Custom Repositories & Locking — Interview Questions
  • JPA Entity Mapping, Associations & Cascades — Interview Questions
  • JPQL vs Native Queries in Depth — Interview Questions
  • Hibernate Caching — First-Level, Second-Level & Query Cache — Interview Questions
  • Lazy vs Eager Loading, LazyInitializationException & N+1 — Interview Questions
  • JPA Transactions, Propagation, Isolation & Dirty Checking — Interview Questions
  • SQL vs NoSQL, Indexing & Query Tuning — Interview Questions
  • Database Scaling, Replication, Pooling & Consistency Models — Interview Questions
  • Redis, Search, Time-Series, CDC & Transactional Data Modelling — Interview Questions

Testing Strategy & API Design

  • Spring Boot Test Slices, Context & Test Strategy — Interview Questions
  • Testing Web, Persistence, Security, Async & Messaging in Spring Boot — Interview Questions
  • JUnit 5 & Mockito, Advanced — Interview Questions
Chaturmind
← Java Interview Prep: 8+ Years (Senior & Lead)

Expert Core Java

  • Tricky Java Output, Operators & OOP Edge Cases — Interview Questions
  • Tricky Exceptions, Memory & Keyword Questions — Interview Questions
  • Classic Java Language Questions, Senior-Grade Answers — Interview Questions
  • Classic Collections, Threads & JDK APIs, Senior-Grade Answers — Interview Questions
  • Reflection, Dynamic Proxies, final & Modern OOP Design — Interview Questions

JVM Internals & Performance

  • Class Loading, Bytecode & Object Layout — Interview Questions
  • JIT Compilation & Runtime Optimisations — Interview Questions
  • Garbage Collectors Deep Dive — Interview Questions
  • JVM Tuning, GC Logs & Memory Footprint — Interview Questions
  • Memory Leaks, OutOfMemoryErrors & Profiling Tools — Interview Questions
  • Modules, Agents & Advanced JVM APIs — Interview Questions

Collections & Concurrency at Scale

  • Collections Internals & Complexity — Interview Questions
  • Iterators, Comparators & Ordering Contracts — Interview Questions
  • Concurrent Collections, Queues & Lock-Free Structures — Interview Questions
  • Threads, Executors & ForkJoin Internals — Interview Questions
  • Locks, Atomics, CAS & Synchronizers — Interview Questions
  • Java Memory Model, volatile, Fences & ThreadLocal — Interview Questions
  • Deadlock, Livelock, Starvation & Concurrent Design — Interview Questions
  • CompletableFuture, Parallel Streams & Non-Blocking I/O — Interview Questions

Modern Java (8 to 21+)

  • Lambdas & Functional Interfaces Internals — Interview Questions
  • Streams & Collectors Deep Dive — Interview Questions
  • Optional & Interface Default/Static Methods — Interview Questions
  • Java 9–25 Features & Virtual Threads — Interview Questions

Design Patterns, SOLID & Clean Code

  • Design Pattern Trade-offs & Combinations — Interview Questions
  • SOLID, Clean Code & Anti-Patterns — Interview Questions

Spring & Spring Boot Internals

  • IoC, Dependency Injection & Bean Lifecycle Internals — Interview Questions
  • Spring AOP, Proxies & @Async Internals — Interview Questions
  • Spring Configuration, Auto-Configuration & Custom Starters — Interview Questions
  • Spring MVC & REST Internals, Exception Frameworks — Interview Questions
  • Spring Security Advanced Internals — Interview Questions
  • Spring WebFlux, Reactor & R2DBC — Interview Questions
  • Spring Cloud, Observability & Distributed Tracing — Interview Questions
  • Spring Boot 3, Native Images & Production Scenarios — Interview Questions

JPA, Hibernate & Databases at Scale

  • Spring Data JPA — Queries, Projections, Custom Repositories & Locking — Interview Questions
  • JPA Entity Mapping, Associations & Cascades — Interview Questions
  • JPQL vs Native Queries in Depth — Interview Questions
  • Hibernate Caching — First-Level, Second-Level & Query Cache — Interview Questions
  • Lazy vs Eager Loading, LazyInitializationException & N+1 — Interview Questions
  • JPA Transactions, Propagation, Isolation & Dirty Checking — Interview Questions
  • SQL vs NoSQL, Indexing & Query Tuning — Interview Questions
  • Database Scaling, Replication, Pooling & Consistency Models — Interview Questions
  • Redis, Search, Time-Series, CDC & Transactional Data Modelling — Interview Questions

Testing Strategy & API Design

  • Spring Boot Test Slices, Context & Test Strategy — Interview Questions
  • Testing Web, Persistence, Security, Async & Messaging in Spring Boot — Interview Questions
  • JUnit 5 & Mockito, Advanced — Interview Questions
HomeLearnJava Interview PrepJava Interview Prep: 8+ Years (Senior & Lead)Collections & Concurrency at Scale
✓ FreeAdvanced· 11 min read

Java Memory Model, volatile, Fences & ThreadLocal — Interview Questions

Visibility vs atomicity, what volatile guarantees and what it does not, when volatile is insufficient, happens-before, why double-checked locking needs volatile, instruction reordering and CPU caches, memory barriers/fences and how Java implements them (volatile, VarHandle fences), false sharing and @Contended, VarHandle, AtomicStampedReference, limits of atomics, ThreadLocal internals, ThreadLocal memory leaks and ScopedValue.

Published September 25, 2026


How to use this lesson

JMM questions separate "knows the keywords" from "understands the hardware and the spec". Anchor every answer in happens-before:

  • a write is guaranteed visible to a read only if a happens-before edge connects them;
  • volatile, locks, thread start and join, final fields and concurrent collections are the ways to create those edges.

Q1. What's the difference between visibility and atomicity?

Short answer:

  • Visibility: whether a write by one thread is seen by another. Without synchronisation, a thread may keep reading a stale value from a register or cache, or the compiler may hoist the read out of a loop, forever.
  • Atomicity: whether an operation happens as one indivisible step. count++ is three steps (read, add, write), so two threads can interleave, and lose an update, even with perfect visibility.

volatile gives visibility and ordering, but not compound atomicity. Atomics give atomic single-variable updates. Locks give both, for whole blocks.

private boolean running = true;          // BUG: without volatile, this loop may never see the update
void stop() { running = false; }
void loop() { while (running) { work(); } }

Learn it in depth → volatile and the Memory Model

Q2. What does volatile guarantee, and what doesn't it guarantee?

Short answer:

  • It guarantees:
    • visibility: a read sees the most recent write in the synchronisation order;
    • ordering: a volatile write happens-before every subsequent read of that variable, so all writes made before the volatile write are visible to a thread that later reads it (safe publication);
    • atomic reads and writes of long/double (which aren't guaranteed atomic otherwise).
  • It doesn't guarantee:
    • atomicity of compound operations (x++, check-then-set);
    • mutual exclusion;
    • consistency across several variables (each is individually visible, but there's no joint invariant);
    • visibility into the state of an object whose reference is volatile but which is mutated afterwards.

Q3. When isn't volatile enough, so you need synchronisation?

Short answer: Whenever correctness depends on more than a single independent write:

  • read-modify-write on the variable (count++, balance -= x): use an atomic or a lock;
  • check-then-act (if (!initialised) init();): two threads can both see false;
  • invariants across several fields (min <= max, or a pair of balance and version);
  • mutable objects behind a volatile reference that are modified in place (use immutable replacements, or locks).

volatile is right for flags, status fields written by one thread, and publishing immutable objects.

Q4. Can changes to a volatile variable be atomic? Give examples where they aren't.

Short answer: A single read or write of a volatile is atomic (including long/double). Compound operations aren't:

volatile int hits;
hits++;                                  // NOT atomic: read → +1 → write; concurrent increments are lost

volatile long max;
if (value > max) max = value;            // NOT atomic: check-then-act race

volatile List<String> names = new ArrayList<>();
names.add("x");                          // NOT safe: the reference is volatile, but the ArrayList isn't thread-safe

The fixes: AtomicInteger.incrementAndGet()/LongAdder, AtomicLong.accumulateAndGet(value, Math::max), and immutable lists replaced through an AtomicReference, or concurrent collections.

Q5. What is the happens-before relationship in the Java Memory Model?

Short answer: A partial ordering guaranteeing that if action A happens-before action B, then A's effects are visible to B, and they appear ordered. The key rules:

  1. Program order: within a thread, each action happens-before the later ones.
  2. Monitor lock: an unlock happens-before every subsequent lock of the same monitor.
  3. volatile: a write happens-before every subsequent read of the same variable.
  4. Thread start: t.start() happens-before any action in t.
  5. Thread termination: actions in t happen-before another thread returns from t.join(), or sees t.isAlive() == false.
  6. Interruption: interrupt() happens-before the interrupted thread detects it.
  7. Final fields: the end of a constructor happens-before any read of the final fields through a properly published reference.
  8. Transitivity: A → B and B → C means A → C.

java.util.concurrent builds on these rules: a put into a ConcurrentHashMap or BlockingQueue happens-before the get/take that retrieves the element, and task submission happens-before execution.

A data race is two conflicting accesses (at least one of them a write) not ordered by happens-before. Programs free of data races are sequentially consistent (the DRF-SC guarantee).

Q6. Why is double-checked locking safe only with volatile?

Short answer: instance = new Singleton() isn't one step. It's: allocate → run the constructor → publish the reference. Without volatile, the compiler or CPU can reorder the publication before the constructor finishes. A second thread doing the unsynchronised first check then sees a non-null but partially constructed object, and uses fields that haven't been initialised. volatile (Java 5+ semantics) forbids that reordering: the volatile write comes after the constructor's writes, and the reader's volatile read happens-after it.

public final class Config {
    private static volatile Config instance;                 // volatile is essential
    public static Config get() {
        Config local = instance;                             // one volatile read on the fast path
        if (local == null) {
            synchronized (Config.class) {
                local = instance;
                if (local == null) instance = local = load();
            }
        }
        return local;
    }
}

Usually, prefer the holder idiom, or an enum.

Q7. How do instruction reordering and CPU caches affect multithreading?

Short answer:

  • Compilers (javac, and above all the JIT) and CPUs reorder independent operations for speed: hoisting loads out of loops, delaying stores, executing out of order.
  • CPUs have store buffers and per-core caches. A core's write may sit in its store buffer, invisible to other cores for a while, and cores may observe writes in different orders. x86 is relatively strong (TSO); ARM and POWER are weak, so bugs appear on ARM servers (Graviton) or Apple Silicon that never showed on x86.
  • Within one thread, the results look sequential ("as-if-serial"). Across threads, without happens-before edges, another thread can see writes out of order, stale values, or never see them.

The JMM lets you write portable code: use volatile, locks or atomics, and the JVM inserts whatever barriers each platform needs.

Q8. What are memory barriers (fences), and how are they implemented in Java? What is a memory fence, and why does it matter?

Short answer: A memory barrier is an instruction that restricts reordering of memory operations across it, and forces visibility (draining store buffers, invalidating stale data). The classic kinds are LoadLoad, LoadStore, StoreStore and StoreLoad. StoreLoad is the most expensive: mfence or a lock-prefixed instruction on x86.

In Java:

  • you rarely write barriers yourself; the JIT emits them for volatile accesses (release semantics on a write, acquire semantics on a read, and a StoreLoad after volatile writes on x86), for monitor enter and exit, final field freezes, and atomic operations;
  • explicit fences are available through VarHandle: VarHandle.fullFence(), acquireFence(), releaseFence(), loadLoadFence(), storeStoreFence() (Java 9). Previously, Unsafe.*Fence;
  • the VarHandle access modes give finer control: getAcquire/setRelease, getOpaque/setOpaque, getVolatile/setVolatile.

Why it matters: fences are the mechanism behind every visibility guarantee. They also have a cost, which is why lock-free algorithms use the weakest ordering that's still correct.

Q9. What is false sharing?

Short answer: CPUs cache memory in 64-byte cache lines. If two threads write different variables that share a cache line, each write invalidates the line in the other core's cache (the MESI protocol). The line ping-pongs between cores, and performance collapses, even though the threads don't share data logically. It typically hits per-thread counters in an array, or adjacent fields updated by different threads.

Fixes:

  • Padding: separate the hot fields by at least 64 (often 128) bytes.
  • @jdk.internal.vm.annotation.Contended: used internally by LongAdder's Cell, ConcurrentHashMap's counter cells, and Thread's fields. For application code, it needs --add-exports and -XX:-RestrictContended.
  • Per-thread data kept apart (thread-local accumulation, merged later).

Detect it with perf c2c, or by benchmarking with and without padding.

Q10. What is VarHandle?

Short answer: A typed, strongly controlled reference to a variable (Java 9, java.lang.invoke): an instance field, a static field, an array element, or a memory segment. It offers:

  • several memory-ordering modes: plain, opaque, acquire/release, volatile;
  • atomic operations: compareAndSet, compareAndExchange, getAndAdd, getAndBitwiseOr, weak CAS;
  • fences.

It's the supported replacement for sun.misc.Unsafe field access and CAS, and java.util.concurrent is built on it. It avoids the per-object overhead of AtomicLong wrappers when you need atomics on many objects' fields.

class Node {
    volatile Node next;
    private static final VarHandle NEXT;
    static {
        try { NEXT = MethodHandles.lookup().findVarHandle(Node.class, "next", Node.class); }
        catch (ReflectiveOperationException e) { throw new ExceptionInInitializerError(e); }
    }
    boolean casNext(Node expected, Node update) { return NEXT.compareAndSet(this, expected, update); }
}

Q11. How does AtomicStampedReference work?

Short answer: It holds a (reference, int stamp) pair, updated atomically together. compareAndSet(expectedRef, newRef, expectedStamp, newStamp) succeeds only if both the reference and the stamp match. Incrementing the stamp on every update means an A → B → A change is detected (the stamp moved from 1 to 3), which solves the ABA problem in lock-free structures and object pools. Internally, it CASes an immutable Pair object, so each update allocates. AtomicMarkableReference is the boolean-mark variant, used for logical deletion flags.

Q12. What are the limitations of the Atomic* classes?

Short answer:

  • They only make a single variable atomic. There are no multi-variable transactions.
  • CAS contention under heavy writes means retry storms (use LongAdder).
  • They're susceptible to ABA (use stamped references).
  • Update functions may be retried, so they must be pure.
  • There's per-instance allocation overhead for many small atomics (use VarHandle or field updaters).
  • They don't compose with blocking waits: "wait until the counter reaches N" needs latches or conditions.

Q13. What is ThreadLocal, and how does it work behind the scenes?

Short answer: A ThreadLocal<T> gives each thread its own independent copy of a value. It's used for per-thread context (security context, transaction and MDC in logging frameworks, request IDs) and for non-thread-safe helpers (formerly SimpleDateFormat).

The internals:

  • Each Thread object has a ThreadLocalMap field.
  • The map's keys are weak references to the ThreadLocal instances, and its values are strong references.
  • get()/set() look up the current thread's map, using the ThreadLocal's precomputed hash (an open-addressing table with linear probing).
  • InheritableThreadLocal copies values into child threads when they're created, not into pool threads that are reused.
private static final ThreadLocal<DecimalFormat> FMT = ThreadLocal.withInitial(() -> new DecimalFormat("#,##0.00"));
String format(BigDecimal v) { return FMT.get().format(v); }

Q14. Can ThreadLocal prevent memory leaks, or cause them? What is a ThreadLocal memory leak?

Short answer: It doesn't prevent leaks. It's a common cause of them:

  • Pooled threads live forever (servlet containers, executors), so values set by one request stay attached to the thread after the request ends, unless they're remove()d. That means memory growth, and data leaking between requests (a security bug: user A's context seen by user B).
  • The weak key doesn't save you: if the ThreadLocal object itself becomes unreachable, the key is cleared, but the value stays strongly referenced in the entry. It's only cleaned opportunistically, on later map operations.
  • Classloader leaks: a value whose class came from a web application's loader pins the whole application after a redeploy.

The rules:

  • set in try, and remove() in finally (filters, interceptors);
  • keep ThreadLocals static final;
  • avoid them with virtual threads: millions of threads, each with its own copy, is expensive.

Prefer ScopedValue (final in Java 25, JEP 506): immutable, bounded to a scope, inherited by structured-concurrency subtasks, with no remove() to forget.

static final ScopedValue<RequestContext> CONTEXT = ScopedValue.newInstance();

ScopedValue.where(CONTEXT, new RequestContext(userId, traceId))
           .run(() -> orderService.placeOrder(cmd));      // CONTEXT.get() is available inside; automatically unbound afterwards

Follow-up questions this topic invites — and their answers

Q: What is safe publication? A: Making an object and its state visible to other threads through a happens-before edge: storing it in a volatile or final field, an AtomicReference, a concurrent collection, or under a lock, or initialising it in a static initialiser. Unsafe publication (a plain field) can expose partially constructed objects.

Q: Are final fields always visible to other threads? A: Yes, if the object is properly constructed (this doesn't escape during construction). Other threads then see the final fields' values (and the objects reachable through them, as of construction) without synchronisation. That's the basis of thread-safe immutability.

Q: What's the difference between getOpaque, getAcquire and getVolatile in VarHandle? A: Opaque guarantees the access isn't optimised away, and is coherent for that variable, with no ordering of other accesses. Acquire also prevents later reads and writes moving before it (paired with release writes). Volatile adds full sequential consistency with the other volatile accesses. Use the weakest mode that's still correct.

Q: How do you test concurrency correctness? A: With jcstress (the OpenJDK harness that explores reorderings on real hardware), Lincheck (linearizability checking), stress tests with many threads plus assertions, and running on weakly ordered hardware (ARM) in CI. Use static analysis for @GuardedBy violations.

Previous

Locks, Atomics, CAS & Synchronizers — Interview Questions

Next

Deadlock, Livelock, Starvation & Concurrent Design — Interview Questions

AI Tutor

Lesson: Java Memory Model, volatile, Fences & ThreadLocal — Interview Questions

Quick actions

AI responses can be inaccurate. Verify critical information.