👋hi,我不是一名外包公司的员工,也不会偷吃茶水间的零食,我的梦想是能写高端CRUD
🔥 2025本人正在沉淀中... 博客更新速度++
👍 欢迎点赞、收藏、关注,跟上我的更新节奏
📚欢迎订阅专栏,专栏名《在2B工作中寻求并发是否搞错了什么》
本文涉及InheritableThreadLocal和TTL,从源码的角度,分别分析它们是怎么实现父子线程传递的。建议先了解ThreadLocal。
【源码】【Java并发】【ThreadLocal】适合中学者体质的ThreadLocal源码阅读
【Java并发】【ThreadLocal】适合初学体质的ThreadLocal
ThreadLocal存在的问题
众所周知,ThreadLocal并没有解决父子间线程传递的问题,比如下面的代码。
java
public static void main(String[] args) throws InterruptedException {
ThreadLocal<String> threadLocal = new ThreadLocal<>();
// threadLocal set值
threadLocal.set("main thread info");
System.out.println("main thread get: " + threadLocal.get());
// 启动子线程
new Thread(() -> System.out.println("sub thread get: " + threadLocal.get())).start();
threadLocal.remove();
}
输出结果
shell
main thread get: main thread info
sub thread get: null
InheritableThreadLocal实现父子线程传递
将ThreadLocal
换为InheritableThreadLocal
:
java
public static void main(String[] args) throws InterruptedException {
InheritableThreadLocal<String> threadLocal = new InheritableThreadLocal<>();
// threadLocal set值
threadLocal.set("main thread info");
System.out.println("main thread get: " + threadLocal.get());
// 启动子线程
new Thread(() -> System.out.println("sub thread get: " + threadLocal.get())).start();
threadLocal.remove();
}
输出结果
shell
main thread get: main thread info
sub thread get: main thread info
🤔,那我们什么我换成InheritableThreadLocal
就可以了呢?
省流版 :原理就是,主线程创建子线程的时候,子线程会拷贝1份主线程的ThreadLocalMap
。

当我们InheritableThreadLocal
在set
值的时候:
初始化当前线程的ThreadLocalMap
,InheritableThreadLocal
会,new一个ThreadLocalMap
,赋值给当前线程的inheritableThreadLocals
字段。(而ThreadLocal
的逻辑是,new一个ThreadLocalMap
,赋值给当前线程的threadLocals
字段)
java
// ThreadLocal#set
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
map.set(this, value);
} else {
// 👇初始化ThreadLocalMap,createMap调用子类InheritableThreadLocal类的实现
createMap(t, value);
}
}
// InheritableThreadLocal#createMap
void createMap(Thread t, T firstValue) {
// 当前线程的inheritableThreadLocals字段
t.inheritableThreadLocals = new ThreadLocalMap(this, firstValue);
}
// ThreadLocal#createMap
void createMap(Thread t, T firstValue) {
// 当前线程的threadLocals字段
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
聪明的你一定会问了,主播,主播,赋值给inheritableThreadLocals
有什么用呢?
那这可是大有说法的,让我们从Thread类开始说起,当我们在主线程,new Thread()
创建子线程的时候,Thread类的init
方法里有个if判断:
- 我们重点看,
parent.inheritableThreadLocals != null
,因为inheritableThreadLocals
我们在set的时候已经赋值了,所以这里为true。 - 而
inheritThreadLocals
是init
方法的入参,我们很少使用到,这样的方式创建线程Thread(Runnable target, AccessControlContext acc)
,所以大部分情况都是true。
如果这两个条件都为true的话,那么会将主线程的ThreadLocalMap
数据拷贝到子线程的inheritableThreadLocals
里。
java
ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
private void init(ThreadGroup g, Runnable target, String name,
long stackSize) {
init(g, target, name, stackSize, null, true);
}
private void init(ThreadGroup g, Runnable target, String name,
long stackSize, AccessControlContext acc,
boolean inheritThreadLocals) {
...
Thread parent = currentThread();
...
// 重点在这里,将主线程的ThreadLocalMap拷贝到子线程的inheritableThreadLocals里。
if (inheritThreadLocals && parent.inheritableThreadLocals != null)
this.inheritableThreadLocals =
ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
...
createInheritedMap
将主线程的ThreadLocalMap拷贝到子线程里。
java
static ThreadLocalMap createInheritedMap(ThreadLocalMap parentMap) {
return new ThreadLocalMap(parentMap);
}
// 将主线程的ThreadLocalMap数据拷贝到子线程里
private ThreadLocalMap(ThreadLocalMap parentMap) {
Entry[] parentTable = parentMap.table;
int len = parentTable.length;
setThreshold(len);
table = new Entry[len];
for (int j = 0; j < len; j++) {
Entry e = parentTable[j];
if (e != null) {
@SuppressWarnings("unchecked")
ThreadLocal<Object> key = (ThreadLocal<Object>) e.get();
if (key != null) {
Object value = key.childValue(e.value);
Entry c = new Entry(key, value);
// 这里需要重新哈希槽位
int h = key.threadLocalHashCode & (len - 1);
while (table[h] != null)
h = nextIndex(h, len);
table[h] = c;
size++;
}
}
}
}
然后子线程get获取的时候,会去从子线程的inheritableThreadLocals
取。
java
// ThreadLocal#get
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t); // 走InheritableThreadLocal的获取map
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}
// InheritableThreadLocal#get
ThreadLocalMap getMap(Thread t) {
return t.inheritableThreadLocals;
}
现在,聪明的你看懂了InheritableThreadLocal是怎么实现父子线程传递的吗?
那我们引入一个新的问题,如果我用线程池会怎么呢?
java
public static void main(String[] args) throws Exception {
InheritableThreadLocal<String> threadLocal = new InheritableThreadLocal<>();
threadLocal.set("main value 1");
System.out.println("main thread set value: " + threadLocal.get());
ExecutorService executorService = Executors.newFixedThreadPool(1);
executorService.execute(() -> System.out.println("Task1, thread " + Thread.currentThread().getName()+ " get value : " + threadLocal.get()));
Thread.sleep(1000);
threadLocal.set("main value 2"); // 主任务修改最新值
System.out.println("main thread set value: " + threadLocal.get());
executorService.execute(() -> System.out.println("Task2, thread " + Thread.currentThread().getName()+ " get value : " + threadLocal.get()));
threadLocal.remove();
executorService.shutdown();
}
输出结果,聪明的你,一眼就看出来,task2没有get到主线程的最新值。诶嘿,这是为什么呢?
shell
main thread set value: main value 1
Task1, thread pool-1-thread-1 get value : main value 1
main thread set value: main value 2
Task2, thread pool-1-thread-1 get value : main value 1 // 没有获取到主线程的最新值
通过上面的原理讲解,我们知道InheritableThreadLocal
实现父子线程传递参数,是在创建子线程 的时候,拷贝父线程的ThreadlLocalMap
到子线程里。
可是,我们使用了线程池,线程(比如这里的pool-1-thread-1
)是复用的,所以不会创造新的线程了,自然不会从主线程中拷贝新的数据来,所以这里的value没有变化。
不要着急,下面主播还会说说怎么解决这个问题的。
TTL实现父子线程传递
TTL 全称是TransmittableThreadLocal
(由阿里开源),专门解决 线程池场景下父子线程值传递丢失的问题,是增强版的InheritableThreadLocal
。
原理省流版 :是通过装饰任务 + 快照机制 实现线程本地变量的跨线程传递。
我们先来看看TTL的解决:
java
public static void main(String[] args) throws Exception {
TransmittableThreadLocal<String> threadLocal = new TransmittableThreadLocal<>();
threadLocal.set("main value 1");
System.out.println("main thread set value: " + threadLocal.get());
ExecutorService executorService = Executors.newFixedThreadPool(1);
// 需要显示的包装任务
executorService.execute(TtlRunnable.get( () -> System.out.println("Task1, thread " + Thread.currentThread().getName() + " get value : " + threadLocal.get())));
Thread.sleep(1000);
threadLocal.set("main value 2");
System.out.println("main thread set value: " + threadLocal.get());
executorService.execute(TtlRunnable.get( () -> System.out.println("Task2, thread " + Thread.currentThread().getName() + " get value : " + threadLocal.get())));
threadLocal.remove();
executorService.shutdown();
}
输出结果
shell
main thread set value: main value 1
Task1, thread pool-1-thread-1 get value : main value 1
main thread set value: main value 2
Task2, thread pool-1-thread-1 get value : main value 2
⚠️:以下源码阅读,基于TTL版本2.14.2
TTL包装任务
这里,聪明的你,一定会发现,我们向线程池提交任务的时候,它把任务用TtlRunnable
包了一层,为什么要这么包呢?
那我们就看看TtlRunnable
包完之后,run方法都干了什么吧!
java
// TtlRunnable#run
@Override
public void run() {
final Object captured = capturedRef.get(); // 获取父线程本地变量的拷贝
if (captured == null || releaseTtlValueReferenceAfterRun && !capturedRef.compareAndSet(captured, null)) {
throw new IllegalStateException("TTL value reference is released after run!");
}
// 将父线程本地变量赋值给当前线程,并返回当前线程修改之前的本地变量
final Object backup = replay(captured);
try {
runnable.run(); // 执行任务的具体逻辑
} finally {
restore(backup); // 恢复当前线程的本地变量
}
}
这样就很天才的解决了,子线程没有更新到父线程最新本地变量。(后面会说每一步具体是怎么做的,这里只需要了解个大概的流程就行了)
这个工作流程,是一个很经典的CRR模式:
capture方法:抓取线程(父线程)的所有TTL值。
replay方法:在另一个线程(子线程)中,回放在capture方法中抓取的TTL值,并返回 回放前TTL值的备份
restore方法:恢复子线程执行replay方法之前的TTL值(即备份)
holder
我们先从set
方法开始说起吧!因为通过这个,你可以了解到holder
。
这对后续源码capture
相关的阅读很重要。
java
@Override
public final void set(T value) {
if (!disableIgnoreNullValueSemantics && value == null) {
remove();
} else {
super.set(value); // ThreadLocal的set实现
addThisToHolder(); // 放入到一个holder里面
}
}
我们会将当前线程的这个的TransmittableThreadLocal
放到一个holder
里。
java
private void addThisToHolder() {
if (!holder.get().containsKey(this)) {
holder.get().put((TransmittableThreadLocal<Object>) this, null); // WeakHashMap supports null value.
}
}
这个holder
不要觉得很复杂,因为没有WeakHashSet,所以这里才用WeakHashMap。
我们把这个holder
当成Set集合
来用,存当前线程所有的TransmittableThreadLocal
。

java
private static final InheritableThreadLocal<WeakHashMap<TransmittableThreadLocal<Object>, ?>> holder =
new InheritableThreadLocal<WeakHashMap<TransmittableThreadLocal<Object>, ?>>() {
@Override
protected WeakHashMap<TransmittableThreadLocal<Object>, ?> initialValue() {
return new WeakHashMap<>();
}
@Override
protected WeakHashMap<TransmittableThreadLocal<Object>, ?> childValue(WeakHashMap<TransmittableThreadLocal<Object>, ?> parentValue) {
return new WeakHashMap<>(parentValue);
}
};
Transmitter和Transmittee
这里再补充下
Transmitter
和Transmittee
的概念。简单来说就是:
Transmitter
管理所有Transmittee
。
Transmittee
负责做具体的CRR操作。

Transmitter
是管理者 :维护所有 Transmittee
,触发它们的执行(就是执行CRR操作,具体怎么做,下面的代码会说)。
Transmitter
有一个transmitteeSet
来存放所有的Transmittee
。
Transmittee
会在注册的时候,被放入transmitteeSet
里,如下代码所表示:
java
private static final Set<Transmittee<Object, Object>> transmitteeSet = new CopyOnWriteArraySet<>();
static {
registerTransmittee(ttlTransmittee); // 注册ttlTransmittee
registerTransmittee(threadLocalTransmittee); // 注册threadLocalTransmittee
}
// TransmittableThreadLocal.Transmitter#registerTransmittee
// registerTransmittee方法是扩展点,可以注册自己实现的Transmittee
// 注册Transmittee到transmitteeSet里
@SuppressWarnings("unchecked")
public static <C, B> boolean registerTransmittee(@NonNull Transmittee<C, B> transmittee) {
return transmitteeSet.add((Transmittee<Object, Object>) transmittee);
}
Transmittee
是被管理者:仅实现具体逻辑,不参与全局调度。
先来看看Transmittee
接口吧,简单来说就是定义了CRR操作。

java
// TransmittableThreadLocal.Transmitter.Transmittee
// C是Transmittee拍快照的类型 , B是Transmittee备份的类型。
public interface Transmittee<C, B> {
@NonNull
C capture();
@NonNull
B replay(@NonNull C captured);
@NonNull
B clear();
void restore(@NonNull B backup);
}
TTL提供了2种Transmittee
的实现,并且会自动注册的。
java
static {
registerTransmittee(ttlTransmittee); // 注册ttlTransmittee
registerTransmittee(threadLocalTransmittee); // 注册threadLocalTransmittee
}
// TransmittableThreadLocal.Transmitter#ttlTransmittee
private static final Transmittee<HashMap<TransmittableThreadLocal<Object>, Object>, HashMap<TransmittableThreadLocal<Object>, Object>> ttlTransmittee =
new Transmittee<HashMap<TransmittableThreadLocal<Object>, Object>, HashMap<TransmittableThreadLocal<Object>, Object>>() {
...
// TransmittableThreadLocal.Transmitter#threadLocalTransmittee
private static final Transmittee<HashMap<ThreadLocal<Object>, Object>, HashMap<ThreadLocal<Object>, Object>> threadLocalTransmittee =
new Transmittee<HashMap<ThreadLocal<Object>, Object>, HashMap<ThreadLocal<Object>, Object>>() {
...
这里我们简单来说说这两个ttlTransmittee
和threadLocalTransmittee
有啥区别吧。
ttlTransmittee
:就是用于处理我们TransmittableThreadLocal
传递数据。threadLocalTransmittee
:用于,不好把ThreadLocal
改造成TransmittableThreadLocal
传递数据的场景。
capture
有了对holder
的了解,现在我们对拍快照就会更容易看懂,所以,现在,跟上主播的节奏,👀让我们看看是怎么拍快照的。
在TtlRunnable.get()
的时候。
java
@Nullable
@Contract(value = "null -> null; !null -> !null", pure = true)
public static TtlRunnable get(@Nullable Runnable runnable) {
return get(runnable, false, false);
}
@Nullable
@Contract(value = "null, _, _ -> null; !null, _, _ -> !null", pure = true)
public static TtlRunnable get(@Nullable Runnable runnable, boolean releaseTtlValueReferenceAfterRun, boolean idempotent) {
if (runnable == null) return null;
if (runnable instanceof TtlEnhanced) {
// avoid redundant decoration, and ensure idempotency
if (idempotent) return (TtlRunnable) runnable;
else throw new IllegalStateException("Already TtlRunnable!");
}
// 重点在new TtlRunnable构造方法
return new TtlRunnable(runnable, releaseTtlValueReferenceAfterRun);
}
// TtlRunnable的构造方法,这里会捕获快照
private TtlRunnable(@NonNull Runnable runnable, boolean releaseTtlValueReferenceAfterRun) {
this.capturedRef = new AtomicReference<>(capture()); // 捕获父线程本地变量
this.runnable = runnable;
this.releaseTtlValueReferenceAfterRun = releaseTtlValueReferenceAfterRun;
}
首先是到Transmitter(管理者)
,它是怎么捕获的呢:
Transmitter(管理者)
遍历所有的Transmittee
。- 调用每个
Transmittee的
具体捕获逻辑,并将具体的捕获结果。 - 将所有的
Transmittee
和捕获数据,存入一个Map
(Key是Transmittee
,Value是这个Transmittee
捕获逻辑的返回。),再封装成快照返回。
主播也是尽力还原了Snapshot大概长什么样:

java
// TransmittableThreadLocal.Transmitter#capture
@NonNull
public static Object capture() {
final HashMap<Transmittee<Object, Object>, Object> transmittee2Value = newHashMap(transmitteeSet.size());
// 遍历所有的Transmittee,执行具体的capture逻辑。
for (Transmittee<Object, Object> transmittee : transmitteeSet) {
try {
// 执行每个transmittee具体的capture逻辑,并放入map中。
transmittee2Value.put(transmittee, transmittee.capture());
} catch (Throwable t) {
if (logger.isLoggable(Level.WARNING)) {
logger.log(Level.WARNING, "exception when Transmitter.capture for transmittee " + transmittee +
"(class " + transmittee.getClass().getName() + "), just ignored; cause: " + t, t);
}
}
}
// 返回快照
return new Snapshot(transmittee2Value);
}
// 快照,重点是transmittee2Value
// transmittee2Value key:Transmittee value:这个Transmittee捕获的数据
private static class Snapshot {
final HashMap<Transmittee<Object, Object>, Object> transmittee2Value;
public Snapshot(HashMap<Transmittee<Object, Object>, Object> transmittee2Value) {
this.transmittee2Value = transmittee2Value;
}
}
这里我们看匿名类型类,ttlTransmittee
具体capture
方法拍取快照:
java
@NonNull
@Override
public HashMap<TransmittableThreadLocal<Object>, Object> capture() {
final HashMap<TransmittableThreadLocal<Object>, Object> ttl2Value = newHashMap(holder.get().size());
// 遍历holder中存储的TransmittableThreadLocal,并作为key
// value是TransmittableThreadLocal对应Entry的value
for (TransmittableThreadLocal<Object> threadLocal : holder.get().keySet()) {
ttl2Value.put(threadLocal, threadLocal.copyValue());
}
return ttl2Value;
}
// TransmittableThreadLocal#copyValue
private T copyValue() {
return copy(get());
}
// TransmittableThreadLocal#copy
// 这也是一个扩展点,默认就是返回从TTL get到值。
public T copy(T parentValue) {
return parentValue;
}
ok了,快照就这样拍下来了,至此CRR模式的,C(capture)就结束了。
replay
下面我们来看看replay做了什么?
java
// TtlRunnable#run
@Override
public void run() {
final Object captured = capturedRef.get(); // 获取父线程本地变量的拷贝
if (captured == null || releaseTtlValueReferenceAfterRun && !capturedRef.compareAndSet(captured, null)) {
throw new IllegalStateException("TTL value reference is released after run!");
}
// 👇将父线程本地变量赋值给当前线程,并返回当前线程修改之前的本地变量
final Object backup = replay(captured);
try {
runnable.run();
} finally {
restore(backup);
}
}
首先又是到我们的Transmitter(管理者)
执行replay
逻辑:
- 获取新数据的快照。
- 遍历
Transmittee
,执行具体的回放逻辑。 - 保存下每个
Transmittee
,执行replay
方法,返回的数据(一般是回放执行前的本地变量),封装完快照返回。
java
@NonNull
public static Object replay(@NonNull Object captured) {
// 获取新数据的快照
final Snapshot capturedSnapshot = (Snapshot) captured;
// transmittee2Value是用来存放,回放前的本地变量,这个是为了方便后续的恢复。
final HashMap<Transmittee<Object, Object>, Object> transmittee2Value = newHashMap(capturedSnapshot.transmittee2Value.size());
// 遍历快照
for (Map.Entry<Transmittee<Object, Object>, Object> entry : capturedSnapshot.transmittee2Value.entrySet()) {
Transmittee<Object, Object> transmittee = entry.getKey();
try {
Object transmitteeCaptured = entry.getValue();
// transmittee具体执行回放逻辑。
transmittee2Value.put(transmittee, transmittee.replay(transmitteeCaptured));
} catch (Throwable t) {
if (logger.isLoggable(Level.WARNING)) {
logger.log(Level.WARNING, "exception when Transmitter.replay for transmittee " + transmittee +
"(class " + transmittee.getClass().getName() + "), just ignored; cause: " + t, t);
}
}
}
// 返回快照,这个快照的数据,是回放之前,本地变量的数据
return new Snapshot(transmittee2Value);
}
那么,让我们看看ttlTransmittee
是怎么做的吧!简单流程就是:
- 备份旧的本地变量
- 更新新的的本地变量
java
@NonNull
@Override
public HashMap<TransmittableThreadLocal<Object>, Object> replay(@NonNull HashMap<TransmittableThreadLocal<Object>, Object> captured) {
final HashMap<TransmittableThreadLocal<Object>, Object> backup = newHashMap(holder.get().size());
// 遍历当前线程下,holder里所有的TTL
for (final Iterator<TransmittableThreadLocal<Object>> iterator = holder.get().keySet().iterator(); iterator.hasNext(); ) {
TransmittableThreadLocal<Object> threadLocal = iterator.next();
// 回放前的本地变量,放入map中
backup.put(threadLocal, threadLocal.get());
// 避免更新前后TTL数据不一致
if (!captured.containsKey(threadLocal)) {
iterator.remove();
threadLocal.superRemove();
}
}
// 更新本地变量,为入参的快照
setTtlValuesTo(captured);
doExecuteCallback(true);
// 返回回放前的备份数据
return backup;
}
// 将当前线程的本地变量,更新为入参的快照
private static void setTtlValuesTo(@NonNull HashMap<TransmittableThreadLocal<Object>, Object> ttlValues) {
for (Map.Entry<TransmittableThreadLocal<Object>, Object> entry : ttlValues.entrySet()) {
TransmittableThreadLocal<Object> threadLocal = entry.getKey();
threadLocal.set(entry.getValue());
}
}
restore
下面我们再来看看restore做了什么?
java
@Override
public void run() {
final Object captured = capturedRef.get();
if (captured == null || releaseTtlValueReferenceAfterRun && !capturedRef.compareAndSet(captured, null)) {
throw new IllegalStateException("TTL value reference is released after run!");
}
final Object backup = replay(captured);
try {
runnable.run();
} finally {
// 👇恢复当前线程的本地变量
restore(backup);
}
}
首先又是到我们的Transmitter(管理者)
执行restore
逻辑,其实已经很简单了:
- 遍历Transmittee,执行具体的恢复逻辑。
less
public static void restore(@NonNull Object backup) {
for (Map.Entry<Transmittee<Object, Object>, Object> entry : ((Snapshot) backup).transmittee2Value.entrySet()) {
Transmittee<Object, Object> transmittee = entry.getKey();
try {
Object transmitteeBackup = entry.getValue();
// 具体恢复逻辑,入参是备份。
transmittee.restore(transmitteeBackup);
} catch (Throwable t) {
if (logger.isLoggable(Level.WARNING)) {
logger.log(Level.WARNING, "exception when Transmitter.restore for transmittee " + transmittee +
"(class " + transmittee.getClass().getName() + "), just ignored; cause: " + t, t);
}
}
}
}
那么,让我们看看ttlTransmittee
是怎么做的吧!简单流程就是:
- 恢复本地变量
java
@Override
public void restore(@NonNull HashMap<TransmittableThreadLocal<Object>, Object> backup) {
doExecuteCallback(false);
for (final Iterator<TransmittableThreadLocal<Object>> iterator = holder.get().keySet().iterator(); iterator.hasNext(); ) {
TransmittableThreadLocal<Object> threadLocal = iterator.next();
// 避免恢复之后和之前的数据不一致
if (!backup.containsKey(threadLocal)) {
iterator.remove();
threadLocal.superRemove();
}
}
// 恢复之前的本地变量,这个方法,在replay的时候也用到过。
setTtlValuesTo(backup);
}
后话
怎么样,聪明的你是否对线程之间传递数据,有更多的了解了勒?
比如InheritableThreadLocal是怎么实现父子线程传递的?为什么使用了线程池,传递数据就有问题呢? TTL具体又是怎么解决这个问题的呢?
😄聪明的你,一定也有有了答案。