How to Pass Values Between Parent and Child Threads with InheritableThreadLocal and TransmittableThreadLocal
The article explains why regular ThreadLocal cannot share data from a parent thread to its child, introduces InheritableThreadLocal as a built‑in solution, shows its limitation with thread‑pool reuse, and demonstrates how Alibaba's TransmittableThreadLocal overcomes this issue with concrete code examples and output.
InheritableThreadLocal
When a parent thread creates a variable that must be accessed by a child thread, a regular ThreadLocal cannot propagate the value because each thread has its own isolated storage. InheritableThreadLocal copies the parent’s thread‑local map to a newly created child thread.
package com.shepherd.example.juc;
public class InheritableThreadLocalDemo {
public static void main(String[] args) {
ThreadLocal<String> threadLocal = new ThreadLocal<>();
ThreadLocal<String> inheritableThreadLocal = new InheritableThreadLocal<>();
threadLocal.set("父线程数据:threadLocal");
inheritableThreadLocal.set("父线程数据:inheritableThreadLocal");
new Thread(() -> {
System.out.println("子线程获取父类ThreadLocal数据:" + threadLocal.get());
System.out.println("子线程获取父类inheritableThreadLocal数据:" + inheritableThreadLocal.get());
}).start();
}
}Output:
子线程获取父类ThreadLocal数据:null
子线程获取父类inheritableThreadLocal数据:父线程数据:inheritableThreadLocalThe copying occurs in the Thread constructor. new Thread() invokes Thread#init, which creates a new inheritableThreadLocals map from the parent when the flag inheritThreadLocals is true:
public Thread(Runnable target) {
init(null, target, "Thread-" + nextThreadNum(), 0);
}
private void init(ThreadGroup g, Runnable target, String name,
long stackSize, AccessControlContext acc,
boolean inheritThreadLocals) {
// ...
if (inheritThreadLocals && parent.inheritableThreadLocals != null)
this.inheritableThreadLocals =
ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
// ...
}Limitation: the inheritance happens only when a brand‑new thread is created. Thread pools reuse existing threads, so the inheritable map is not refreshed for each task, leading to stale or missing values.
public class InheritableThreadLocalFailDemo {
private static ExecutorService fixedThreadPool = Executors.newFixedThreadPool(1);
public static void main(String[] args) {
InheritableThreadLocal<String> context = new InheritableThreadLocal<>();
context.set("value1-set-in-parent");
fixedThreadPool.submit(() -> System.out.println(context.get()));
context.set("value2-set-in-parent");
fixedThreadPool.submit(() -> System.out.println(context.get()));
}
}Because the pool size is 1, the second task reuses the same worker thread, which still holds the first value, demonstrating the problem.
TransmittableThreadLocal (TTL)
Alibaba open‑source TransmittableThreadLocal captures the current thread‑local values at task submission and restores them when the task runs, even for pooled threads. The following example creates a TTL‑enabled executor and shows value propagation across ordinary threads and thread‑pool tasks.
package com.shepherd.example.juc;
import com.alibaba.ttl.TransmittableThreadLocal;
import com.alibaba.ttl.threadpool.TtlExecutors;
import java.util.concurrent.*;
public class TransmittableThreadLocalTest {
private static ExecutorService executorService =
TtlExecutors.getTtlExecutorService(Executors.newFixedThreadPool(1));
private static TransmittableThreadLocal<LocalBean> ttl = new TransmittableThreadLocal<>();
public static void main(String[] args) throws InterruptedException {
LocalBean localBean = new LocalBean("初始值1");
ttl.set(localBean);
new Thread(() -> {
System.out.println("对普通线程的传递性:" + ttl.get());
localBean.setProp("单一线程更新值2");
}).start();
executorService.execute(() -> {
LocalBean lb = ttl.get();
System.out.println(String.format("线程名称(%s): %s", Thread.currentThread().getName(), lb.getProp()));
lb.setProp("线程池更新值3");
System.out.println(String.format("After set, 线程名称(%s): %s", Thread.currentThread().getName(), ttl.get().getProp()));
});
TimeUnit.SECONDS.sleep(1);
System.out.println(String.format("Main 线程名称(%s): %s", Thread.currentThread().getName(), ttl.get().getProp()));
executorService.execute(() -> {
System.out.println(String.format("线程名称(%s): %s", Thread.currentThread().getName(), ttl.get().getProp()));
ttl.set(new LocalBean("线程池替换localBean"));
});
TimeUnit.SECONDS.sleep(1);
System.out.println(String.format("main 线程名称(%s): %s", Thread.currentThread().getName(), ttl.get()));
}
static class LocalBean {
private String prop;
LocalBean(String p) { this.prop = p; }
public String getProp() { return prop; }
public void setProp(String p) { this.prop = p; }
public String toString() { return "LocalBean(prop=" + prop + ")"; }
}
}Execution output:
对普通线程的传递性:TransmittableThreadLocalTest.LocalBean(prop=初始值1)
线程名称(pool-1-thread-1): 单一线程更新值2
After set, 线程名称(pool-1-thread-1): 线程池更新值3
Main 线程名称(main): 线程池更新值3
线程名称(pool-1-thread-1): 线程池更新值3
main 线程名称(main): TransmittableThreadLocalTest.LocalBean(prop=线程池更新值3)The results confirm that TTL correctly propagates the variable across pooled threads and reflects updates made inside tasks.
Repository with the implementation and further details: https://github.com/alibaba/transmittable-thread-local
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Shepherd Advanced Notes
Dedicated to sharing advanced Java technical insights, daily work snippets, and the power of persistent effort.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
