Home

ThreadLocal 与 InheritableThreadLocal 技术详解

Li

Li Wei

July 18, 20257 min read

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 ordinary ThreadLocal variables
  • inheritableThreadLocals: stores InheritableThreadLocal variables

2.2 The ThreadLocalMap Structure

ThreadLocalMap is an inner class of ThreadLocal, implemented as a custom hash table:

  • key – a weak reference to the ThreadLocal object
  • 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

    • ThreadLocalMap is an implementation detail of ThreadLocal and should not be accessed directly from outside.
    • Defining it as an inner class of ThreadLocal lets us control its visibility.
    • ThreadLocal can access the inner methods and fields of ThreadLocalMap, while external code cannot.
  • Separation of Responsibilities

    • Thread is responsible for holding the ThreadLocalMap instance.
    • ThreadLocal is responsible for operating on the map.
    • This follows the “who uses it, who maintains it” principle.
  • Keeping Thread Lean

    • Placing the implementation of ThreadLocalMap inside Thread would make the Thread class more complex.
    • Thread already carries many core responsibilities; it should not also contain the details of ThreadLocal.
  • Flexibility

    • This design allows the implementation of ThreadLocal to evolve independently of Thread.
    • If the map’s implementation needs to change in the future, only the ThreadLocal class needs modification, not Thread.

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):

  1. Compute the hash of the ThreadLocal object to determine the initial index.
  2. If that slot is occupied, probe forward to find the next free slot.
  3. Store the ThreadLocal and its value in the found slot.

Retrieving (get operation):

  1. Compute the hash of the ThreadLocal object to determine the initial index.
  2. Check the entry at that index; if the key matches, return the value.
  3. If the key does not match, continue probing forward until a matching key is found or a null slot 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 get operation 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 null slot is found.
  • Reference equality – keys are compared with == rather than equals(), guaranteeing that the retrieved entry belongs to the exact same ThreadLocal instance.

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 ThreadLocalMap instances.
  • Modifications to an InheritableThreadLocal in 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 Thread object is created, its threadLocals field is initially null.
    • The map is instantiated only on the first call to set() or get() on a ThreadLocal.
  • For InheritableThreadLocal (inheritableThreadLocals)

    • If the parent thread’s inheritableThreadLocals is null, the child’s field also starts as null (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.

5.2 Special Case of the main Thread

  • The main thread is created by the JVM and has no Java‑level parent thread.
  • Its inheritableThreadLocals are 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 ThreadLocal instance 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 ThreadLocal object itself to be reclaimed

    • If the key were a strong reference, the map would keep the ThreadLocal alive even after the application no longer holds it.
    • Weak references let the ThreadLocal be collected when it becomes unreachable, avoiding leaks of the ThreadLocal object itself.
  • Match the intended lifecycle of a ThreadLocal

    • The lifetime of a ThreadLocal should be governed by the object that holds it, not by the thread that stores its values.
    • When the holder is collected, the ThreadLocal should become eligible for collection as well.
  • Keep the value accessible while the ThreadLocal is alive

    • A strong reference to the value ensures that as long as the ThreadLocal instance exists, its stored value will not be reclaimed unexpectedly.
    • This aligns with developer expectations: if the ThreadLocal is still reachable, its data should be retrievable.
  • Trade‑offs

    • Strong key + strong value → neither the ThreadLocal nor 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 ThreadLocal can be reclaimed; the value may linger until cleaned up by auxiliary mechanisms.
  • Built‑in cleanup

    • Operations such as get(), set(), and remove() automatically purge stale entries whose keys have been cleared.

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.

Keep reading

More related articles from DriftSeas.