Fundamentals 5 min read

Why userId Gets Lost in Async Calls and How ThreadLocal Solves It

The article explains ThreadLocal's lifecycle, why userId disappears when spawning asynchronous threads, compares ThreadLocal, InheritableThreadLocal, and Alibaba's TransmittableThreadLocal, and shows how the latter prevents data contamination in thread‑pool reuse.

Lobster Programming
Lobster Programming
Lobster Programming
Why userId Gets Lost in Async Calls and How ThreadLocal Solves It

ThreadLocal is a common tool; its lifecycle is bound to a thread. The data is stored in a private ThreadLocalMap inside each thread, acting as a thread‑specific repository.

In a typical request, an interceptor writes userId into the current thread’s ThreadLocalMap. When an asynchronous thread is created, the child thread lacks the userId because threads have isolated stack memory, causing the loss.

InheritableThreadLocal allows child threads to inherit values from the parent. Its core source code is:

public class InheritableThreadLocal<T> extends ThreadLocal<T> {
    protected T childValue(T parentValue) {
        return parentValue;
    }
    ThreadLocalMap getMap(Thread t) {
        return t.inheritableThreadLocals;
    }
    void createMap(Thread t, T firstValue) {
        t.inheritableThreadLocals = new ThreadLocalMap(this, firstValue);
    }
}

However, under a thread pool, InheritableThreadLocal fails because the copy occurs only when a thread is created. The article illustrates a scenario:

t1: task A submitted, new thread T1 created, userId copied.

t2: task A finishes, T1 returns to pool with userId still stored.

t3: task B runs, pool reuses T1, no copy occurs, task B reads stale userId from task A.

TransmitableThreadLocal (Alibaba) extends InheritableThreadLocal and ensures the latest parent value is copied before each task execution. Maven dependency:

<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>transmittable-thread-local</artifactId>
    <version>2.14.5</version>
</dependency>

The core mechanism follows the Capture‑Replay‑Restore (CRR) pattern:

Capture: snapshot the context when the task is submitted from the main thread.

Replay: before the child thread runs the task, replay the snapshot, overwriting any dirty data.

Restore: after task completion, clean up to restore the thread’s original state.

TransmitableThreadLocal works as a decorator around Runnable.run(), using replay and restore to guarantee that even when threads are reused, the context matches the current task.

Summary:

InheritableThreadLocal copies parent data only at thread creation, leading to data contamination in thread‑pool reuse.

Alibaba’s open‑source TransmittableThreadLocal solves this by updating the context for each task, preventing stale data.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

Thread poolThreadLocalJava concurrencyInheritableThreadLocalTransmittableThreadLocal
Lobster Programming
Written by

Lobster Programming

Sharing insights on technical analysis and exchange, making life better through technology.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.