library
advancedThreadLocal & 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 formatter3
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 this5
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.