What is ThreadLocal, and when would you use it?
Quick Answer
ThreadLocal<T> gives each thread its own independent, isolated copy of a variable, so concurrent threads reading/writing the 'same' ThreadLocal never see each other's values. It's commonly used for per-thread context that would otherwise require passing extra parameters everywhere (e.g., the current request/user in a web server, a SimpleDateFormat instance, or a per-thread transaction). Care is needed to call remove() when done, especially in thread-pooled environments, to avoid memory leaks and stale data leaking to a reused thread.
Detailed Answer
A ThreadLocal<T> provides a variable where each thread that accesses it sees its own independently initialized copy — reads and writes from one thread are invisible to, and unaffected by, any other thread's use of the same ThreadLocal instance.
private static final ThreadLocal<SimpleDateFormat> FORMATTER =
ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd"));
String format(Date d) {
return FORMATTER.get().format(d); // each thread gets its own SimpleDateFormat instance
}
Common use cases:
- Per-request context in a web server: stashing the current user, request ID, or locale so it's accessible anywhere in the call stack without threading it through every method signature — frameworks like Spring's
RequestContextHolderuse this internally. - Non-thread-safe utility objects: classes like the legacy
SimpleDateFormataren't thread-safe; giving each thread its own instance viaThreadLocalavoids both external synchronization and the cost of creating a new instance per call. - Per-thread transaction/connection tracking in database access frameworks.
Implementation, briefly: each Thread object internally holds a small map (ThreadLocalMap) keyed by ThreadLocal instance, so get()/set() look up the calling thread's own entry.
Pitfall — memory leaks in thread pools: because pooled threads are long-lived and reused across many tasks, a ThreadLocal value set by one task can persist and leak into a later, unrelated task on the same pooled thread if not cleaned up. Always call threadLocal.remove() (typically in a finally block) once the per-task work is done, especially in server/pool environments — otherwise you risk both a memory leak (the value keeps the referenced object alive) and stale-context bugs.