All topics
library
advanced

ThreadLocal & InheritableThreadLocal

Store per-thread data without synchronization and understand the memory leak risk.

A ThreadLocal gives every thread its own copy of a value, so no synchronisation is needed.

ThreadLocal = personal lockers at a gym. Each person (thread) has their own locker (storage). No sharing, no conflicts. But if you leave your stuff and never clean out the locker (remove), it piles up.

Key Concepts

1
    private static final ThreadLocal<SimpleDateFormat> FMT =
        ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd"));
2
    String s = FMT.get().format(date);       // each thread, its own formatter
3
How it is stored. Each Thread object holds a ThreadLocalMap, keyed by the ThreadLocal instance. So get() reads the current thread's map — there is no shared state to protect.
ThreadThreadLocalMapThreadLocalget()
4
Where it is genuinely useful.
    a non-thread-safe helper   SimpleDateFormat, a Random
    request context            the current user, a trace id
    transaction state          Spring's transaction manager uses this
5
The memory leak is the thing to know.
    void handle(Request r) {
      CONTEXT.set(r.user());
      process();
      // no remove -> the user stays in this thread's map
    }
6
In a thread pool the thread never dies, so the value is held for the life of the application — and the next request on that thread may read a previous user's data. That second part is a correctness and security bug, not just a leak.
7
    try {
      CONTEXT.set(r.user());
      process();
    } finally {
      CONTEXT.remove();          // always
    }
8
In a web application, do this in a filter or interceptor so no handler can forget.
9
Why the leak survives garbage collection. The map's key is a weak reference to the ThreadLocal, but the value is strong. So even after the ThreadLocal itself is collected, the value is pinned by the live thread until something calls remove().
ThreadLocalremove()
10
InheritableThreadLocal passes the value to child threads at creation:
InheritableThreadLocal
11
    static final InheritableThreadLocal<String> TRACE = new InheritableThreadLocal<>();
12
The child gets a copy, so later changes do not propagate either way. And it only works for threads the parent creates directly — a task handed to a pool runs on a thread created long before, so the value does not arrive. That makes it unreliable for context propagation in practice.
13
With virtual threads, avoid it. A million threads means a million copies. Scoped values are the replacement, and they are also properly scoped — bound for a block, then gone.
14
What the interviewer is probing.1. "What goes wrong with ThreadLocal in a thread pool?" Probing: the leak, and the security consequence. Stalls: "Nothing, it is per thread." Moves up: the thread is reused, so the value survives the request: a leak, and the next request on that thread can read the previous user's data.
15
2. "Where should remove() be called?" Probing: the discipline. Stalls: "At the end of the method." Moves up: in a finally block, and in a web application from a filter so no handler can forget.
16
3. "Why does the leak survive garbage collection?" Probing: the reference types in the map. Stalls: "It should not." Moves up: the map key is a weak reference to the ThreadLocal but the value is strong, so a live thread pins the value even after the ThreadLocal itself is collected.
17
4. "Is InheritableThreadLocal reliable for propagating context?" Probing: the gap people hit. Stalls: "Yes, children inherit it." Moves up: only for threads the parent creates directly — a task handed to a pool runs on a thread created long before, so the value never arrives.