ThreadLocal 与 InheritableThreadLocal 技术详解
Li Wei
Title: In‑Depth Technical Explanation of ThreadLocal and InheritableThreadLocal
1. Overview
ThreadLocal is a Java utility that provides thread‑local variables, allowing each thread to store and access its own data independently. InheritableThreadLocal is a subclass of ThreadLocal that adds the ability to pass data from a parent thread to its child threads.
2. Basic Architecture of ThreadLocal
2.1 The Dual‑Map Structure in the Thread Class
The Thread class maintains two fields of type ThreadLocalMap:
threadLocals: stores ordinaryThreadLocalvariablesinheritableThreadLocals: storesInheritableThreadLocalvariables
2.2 The ThreadLocalMap Structure
ThreadLocalMap is an inner class of ThreadLocal, implemented as a custom hash table:
- key – a weak reference to the
ThreadLocalobject - value – the thread‑local variable value (strong reference)
2.3 Why ThreadLocalMap Is Defined Inside ThreadLocal While Its Instance Lives in Thread
This seemingly roundabout design is actually intentional:
Encapsulation and Access Control
ThreadLocalMapis an implementation detail ofThreadLocaland should not be accessed directly from outside.- Defining it as an inner class of
ThreadLocallets us control its visibility. ThreadLocalcan access the inner methods and fields ofThreadLocalMap, while external code cannot.
Separation of Responsibilities
Threadis responsible for holding theThreadLocalMapinstance.ThreadLocalis responsible for operating on the map.- This follows the “who uses it, who maintains it” principle.
Keeping
ThreadLean- Placing the implementation of
ThreadLocalMapinsideThreadwould make theThreadclass more complex. Threadalready carries many core responsibilities; it should not also contain the details ofThreadLocal.
- Placing the implementation of
Flexibility
- This design allows the implementation of
ThreadLocalto evolve independently ofThread. - If the map’s implementation needs to change in the future, only the
ThreadLocalclass needs modification, notThread.
- This design allows the implementation of
2.4 Size of ThreadLocalMap and Hash‑Collision Handling
2.4.1 Initial Capacity and Expansion
The initial capacity of a ThreadLocalMap is 16 (defined in the source code).
When the number of occupied slots exceeds two‑thirds of the total capacity, the map expands, doubling its size.
2.4.2 Resolving Hash Collisions
ThreadLocalMap uses open addressing with linear probing to resolve collisions, rather than the bucket‑list or red‑black‑tree approach used by HashMap.
Storing (set operation):
- Compute the hash of the
ThreadLocalobject to determine the initial index. - If that slot is occupied, probe forward to find the next free slot.
- Store the
ThreadLocaland its value in the found slot.
Retrieving (get operation):
- Compute the hash of the
ThreadLocalobject to determine the initial index. - Check the entry at that index; if the key matches, return the value.
- If the key does not match, continue probing forward until a matching key is found or a
nullslot is encountered.
2.4.3 Why the Wrong Value Is Never Returned
Even though open addressing may place a ThreadLocal entry at a position different from its original hash, ThreadLocalMap performs an exact match when retrieving:
- Exact match – the
getoperation compares both the index and the entry’s key (reference equality). - Linear probing – if the initial slot does not match, probing continues until the correct key or a
nullslot is found. - Reference equality – keys are compared with
==rather thanequals(), guaranteeing that the retrieved entry belongs to the exact sameThreadLocalinstance.
Thus, even in the presence of collisions, the correct value is always returned and no “mix‑up” occurs.
3. How InheritableThreadLocal Works
3.1 Overridden Core Methods
InheritableThreadLocal overrides three pivotal methods of ThreadLocal:
** (the original code snippets are omitted here)
3.2 Variable Transfer When a Child Thread Is Created
When a parent thread creates a child thread via new Thread(), the init() method in the Thread constructor performs the value transfer:
**
3.3 Variable Copy Mechanism
The copying process is implemented by the createInheritedMap method inside ThreadLocal:
**
4. Isolation and Visibility of Values Across Threads
4.1 Value Isolation
- The child thread receives a copy of the parent’s
inheritableThreadLocals. - Parent and child operate on independent
ThreadLocalMapinstances. - Modifications to an
InheritableThreadLocalin the child after creation do not affect the parent.
4.2 One‑Way Propagation
- Propagation is unidirectional (parent → child).
- It occurs only once, at the moment the child thread is created.
- Subsequent changes are not reflected in either direction.
4.3 Example Code
**
5. Initialization Mechanism of ThreadLocalMap
5.1 Initialization Details
For ordinary
ThreadLocal(threadLocals)- When a
Threadobject is created, itsthreadLocalsfield is initiallynull. - The map is instantiated only on the first call to
set()orget()on aThreadLocal.
- When a
For
InheritableThreadLocal(inheritableThreadLocals)- If the parent thread’s
inheritableThreadLocalsisnull, the child’s field also starts asnull(lazy initialization). - If the parent’s map is non‑null, the child’s map is created during thread construction and the parent’s entries are copied over.
- If the parent thread’s
5.2 Special Case of the main Thread
- The
mainthread is created by the JVM and has no Java‑level parent thread. - Its
inheritableThreadLocalsare created lazily on first use and inherit no values.
6. Memory‑Leak Issues and Solutions
6.1 Entry Design and Potential Leaks
ThreadLocalMap.Entry uses a weak‑reference key + strong‑reference value layout.
Potential problem:
- When a
ThreadLocalinstance is no longer referenced elsewhere, its key can be reclaimed by the garbage collector. - However, the value remains strongly referenced by the entry, preventing it from being collected.
- If the owning thread lives for a long time, the value may leak.
6.2 Rationale Behind This Design
Allow the
ThreadLocalobject itself to be reclaimed- If the key were a strong reference, the map would keep the
ThreadLocalalive even after the application no longer holds it. - Weak references let the
ThreadLocalbe collected when it becomes unreachable, avoiding leaks of theThreadLocalobject itself.
- If the key were a strong reference, the map would keep the
Match the intended lifecycle of a
ThreadLocal- The lifetime of a
ThreadLocalshould be governed by the object that holds it, not by the thread that stores its values. - When the holder is collected, the
ThreadLocalshould become eligible for collection as well.
- The lifetime of a
Keep the value accessible while the
ThreadLocalis alive- A strong reference to the value ensures that as long as the
ThreadLocalinstance exists, its stored value will not be reclaimed unexpectedly. - This aligns with developer expectations: if the
ThreadLocalis still reachable, its data should be retrievable.
- A strong reference to the value ensures that as long as the
Trade‑offs
- Strong key + strong value → neither the
ThreadLocalnor the value can be reclaimed. - Weak key + weak value → the value might disappear at any time, contradicting the purpose of
ThreadLocal. - Weak key + strong value (current design) → the
ThreadLocalcan be reclaimed; the value may linger until cleaned up by auxiliary mechanisms.
- Strong key + strong value → neither the
Built‑in cleanup
- Operations such as
get(),set(), andremove()automatically purge stale entries whose keys have been cleared.
- Operations such as
7. Tips and Best Practices
7.1 Shallow‑Copy Issue
InheritableThreadLocal performs a shallow copy when inheriting values:
- Immutable objects (e.g.,
String,Integer) are safe. - Mutable objects can cause side effects: changes made by the child thread affect the parent’s instance.
7.2 Customizing Inheritance
Override the childValue() method to define how a value is transformed before being passed to the child thread.
7.3 Thread‑Pool Considerations
InheritableThreadLocal propagates values only at thread creation. In a thread‑pool scenario this can be problematic because:
- Worker threads are reused across tasks.
- The inherited values may be stale and not reflect the current parent thread’s state.
For thread pools, consider using extensions such as Alibaba’s TransmittableThreadLocal, which explicitly handles value transmission across reused threads.
8. Summary
ThreadLocal and InheritableThreadLocal provide thread‑local storage, with the latter enabling parent‑to‑child data propagation through clever design. Their internal workings involve the dual‑map structure in the Thread class, the value‑copying mechanism during thread creation, and a weak‑reference key strategy to mitigate memory leaks.
Understanding these implementation details and the associated leak risks helps you use these tools correctly and avoid common pitfalls. Remember to call remove() after you’re done with a ThreadLocal, especially when working with thread pools.
Originally written by Li Wei (李唯_) and published in Chinese on 后端技术栈全书 (Full-Stack Backend Engineering). Translated and adapted for DriftSeas with permission.