【源码】【Java并发】从InheritableThreadLocal和TTL源码的角度来看父子线程传递

简介: 本文涉及InheritableThreadLocal和TTL,从源码的角度,分别分析它们是怎么实现父子线程传递的。建议先了解ThreadLocal。

👋hi,我不是一名外包公司的员工,也不会偷吃茶水间的零食,我的梦想是能写高端CRUD

🔥 2025本人正在沉淀中... 博客更新速度++

👍 欢迎点赞、收藏、关注,跟上我的更新节奏

📚欢迎订阅专栏,专栏名《在2B工作中寻求并发是否搞错了什么》

本文涉及InheritableThreadLocal和TTL,从源码的角度,分别分析它们是怎么实现父子线程传递的。

建议先了解ThreadLocal。

【源码】【Java并发】【ThreadLocal】适合中学者体质的ThreadLocal源码阅读

【Java并发】【ThreadLocal】适合初学体质的ThreadLocal

image.png

ThreadLocal存在的问题

众所周知,ThreadLocal并没有解决父子间线程传递的问题,比如下面的代码。

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();
}

输出结果

main thread get: main thread info
sub thread get: null

InheritableThreadLocal实现父子线程传递

ThreadLocal换为InheritableThreadLocal:

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();
}

输出结果

main thread get: main thread info
sub thread get: main thread info

🤔,那我们什么我换成InheritableThreadLocal就可以了呢?

省流版:原理就是,主线程创建子线程的时候,子线程会拷贝1份主线程的ThreadLocalMap

image.png

当我们InheritableThreadLocalset值的时候:

初始化当前线程的ThreadLocalMapInheritableThreadLocal会,new一个ThreadLocalMap,赋值给当前线程的inheritableThreadLocals字段。(而ThreadLocal的逻辑是,new一个ThreadLocalMap,赋值给当前线程的threadLocals字段)

// 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。
  • inheritThreadLocalsinit方法的入参,我们很少使用到,这样的方式创建线程Thread(Runnable target, AccessControlContext acc),所以大部分情况都是true。

如果这两个条件都为true的话,那么会将主线程的ThreadLocalMap数据拷贝到子线程的inheritableThreadLocals里。

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拷贝到子线程里。

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取。

// 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是怎么实现父子线程传递的吗?

那我们引入一个新的问题,如果我用线程池会怎么呢?

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到主线程的最新值。诶嘿,这是为什么呢?

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的解决:

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();
}

输出结果

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方法都干了什么吧!

// 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相关的阅读很重要。

@Override
public final void set(T value) {
   
    if (!disableIgnoreNullValueSemantics && value == null) {
   
        remove();
    } else {
   
        super.set(value);    // ThreadLocal的set实现
        addThisToHolder();    // 放入到一个holder里面
    }
}

我们会将当前线程的这个的TransmittableThreadLocal放到一个holder里。

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

image.png

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

这里再补充下TransmitterTransmittee的概念。简单来说就是:

Transmitter管理所有Transmittee

Transmittee负责做具体的CRR操作。

image.png

Transmitter 是管理者:维护所有 Transmittee,触发它们的执行(就是执行CRR操作,具体怎么做,下面的代码会说)。

Transmitter有一个transmitteeSet来存放所有的Transmittee

Transmittee会在注册的时候,被放入transmitteeSet里,如下代码所表示:

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操作。

image.png

// 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的实现,并且会自动注册的。

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>>() {
   
...

这里我们简单来说说这两个ttlTransmitteethreadLocalTransmittee有啥区别吧。

  • ttlTransmittee:就是用于处理我们TransmittableThreadLocal传递数据。
  • threadLocalTransmittee:用于,不好把ThreadLocal改造成TransmittableThreadLocal传递数据的场景。

capture

有了对holder的了解,现在我们对拍快照就会更容易看懂,所以,现在,跟上主播的节奏,👀让我们看看是怎么拍快照的。

TtlRunnable.get()的时候。

@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(管理者),它是怎么捕获的呢:

  1. Transmitter(管理者)遍历所有的Transmittee
  2. 调用每个Transmittee的具体捕获逻辑,并将具体的捕获结果。
  3. 将所有的Transmittee和捕获数据,存入一个Map(Key是Transmittee,Value是这个Transmittee捕获逻辑的返回。),再封装成快照返回。

主播也是尽力还原了Snapshot大概长什么样:

image.png

// 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方法拍取快照:

@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做了什么?

// 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逻辑:

  1. 获取新数据的快照。
  2. 遍历Transmittee,执行具体的回放逻辑。
  3. 保存下每个Transmittee,执行replay方法,返回的数据(一般是回放执行前的本地变量),封装完快照返回。
@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是怎么做的吧!简单流程就是:

  1. 备份旧的本地变量
  2. 更新新的的本地变量
@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做了什么?

@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逻辑,其实已经很简单了:

  1. 遍历Transmittee,执行具体的恢复逻辑。
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是怎么做的吧!简单流程就是:

  1. 恢复本地变量
@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具体又是怎么解决这个问题的呢?

😄聪明的你,一定也有有了答案。

参考

GitHub - alibaba/transmittable-thread-local: 📌 a missing Java std lib(simple & 0-dependency) for framework/middleware, provide an enhanced InheritableThreadLocal that transmits values between threads even using thread pooling components.

目录
相关文章
|
26天前
|
前端开发 Java 关系型数据库
基于Java+Springboot+Vue开发的鲜花商城管理系统源码+运行
基于Java+Springboot+Vue开发的鲜花商城管理系统(前后端分离),这是一项为大学生课程设计作业而开发的项目。该系统旨在帮助大学生学习并掌握Java编程技能,同时锻炼他们的项目设计与开发能力。通过学习基于Java的鲜花商城管理系统项目,大学生可以在实践中学习和提升自己的能力,为以后的职业发展打下坚实基础。技术学习共同进步
119 7
|
1月前
|
消息中间件 算法 安全
JUC并发—1.Java集合包底层源码剖析
本文主要对JDK中的集合包源码进行了剖析。
|
1月前
|
人工智能 安全 Java
智慧工地源码,Java语言开发,微服务架构,支持分布式和集群部署,多端覆盖
智慧工地是“互联网+建筑工地”的创新模式,基于物联网、移动互联网、BIM、大数据、人工智能等技术,实现对施工现场人员、设备、材料、安全等环节的智能化管理。其解决方案涵盖数据大屏、移动APP和PC管理端,采用高性能Java微服务架构,支持分布式与集群部署,结合Redis、消息队列等技术确保系统稳定高效。通过大数据驱动决策、物联网实时监测预警及AI智能视频监控,消除数据孤岛,提升项目可控性与安全性。智慧工地提供专家级远程管理服务,助力施工质量和安全管理升级,同时依托可扩展平台、多端应用和丰富设备接口,满足多样化需求,推动建筑行业数字化转型。
76 5
|
1月前
|
前端开发 Java 物联网
智慧班牌源码,采用Java + Spring Boot后端框架,搭配Vue2前端技术,支持SaaS云部署
智慧班牌系统是一款基于信息化与物联网技术的校园管理工具,集成电子屏显示、人脸识别及数据交互功能,实现班级信息展示、智能考勤与家校互通。系统采用Java + Spring Boot后端框架,搭配Vue2前端技术,支持SaaS云部署与私有化定制。核心功能涵盖信息发布、考勤管理、教务处理及数据分析,助力校园文化建设与教学优化。其综合性和可扩展性有效打破数据孤岛,提升交互体验并降低管理成本,适用于日常教学、考试管理和应急场景,为智慧校园建设提供全面解决方案。
242 70
|
8天前
|
JavaScript Java 关系型数据库
家政系统源码,java版本
这是一款基于SpringBoot后端框架、MySQL数据库及Uniapp移动端开发的家政预约上门服务系统。
家政系统源码,java版本
|
19天前
|
供应链 JavaScript 前端开发
Java基于SaaS模式多租户ERP系统源码
ERP,全称 Enterprise Resource Planning 即企业资源计划。是一种集成化的管理软件系统,它通过信息技术手段,将企业的各个业务流程和资源管理进行整合,以提高企业的运营效率和管理水平,它是一种先进的企业管理理念和信息化管理系统。 适用于小微企业的 SaaS模式多租户ERP管理系统, 采用最新的技术栈开发, 让企业简单上云。专注于小微企业的应用需求,如企业基本的进销存、询价,报价, 采购、销售、MRP生产制造、品质管理、仓库库存管理、财务应收付款, OA办公单据、CRM等。
100 23
|
1月前
|
Java
【源码】【Java并发】【ReentrantLock】适合中学者体质的ReentrantLock源码阅读
因为本文说的是ReentrantLock源码,因此会默认,大家对AQS有基本的了解(比如同步队列、条件队列大概> 长啥样?)。 不懂AQS的小朋友们,你们好呀!也欢迎先看看这篇
81 13
【源码】【Java并发】【ReentrantLock】适合中学者体质的ReentrantLock源码阅读
|
1月前
|
Java
【源码】【Java并发】【ConcurrentHashMap】适合中学体质的ConcurrentHashMap
本文深入解析了ConcurrentHashMap的实现原理,涵盖JDK 7与JDK 8的区别、静态代码块、构造方法、put/get/remove核心方法等。JDK 8通过Node数组+链表/红黑树结构优化并发性能,采用CAS和synchronized实现高效锁机制。文章还详细讲解了hash计算、表初始化、扩容协助及计数更新等关键环节,帮助读者全面掌握ConcurrentHashMap的工作机制。
72 6
【源码】【Java并发】【ConcurrentHashMap】适合中学体质的ConcurrentHashMap
|
1月前
|
Java
【源码】【Java并发】【LinkedBlockingQueue】适合中学体质的LinkedBlockingQueue入门
前言 有了前文对简单实用的学习 【Java并发】【LinkedBlockingQueue】适合初学体质的LinkedBlockingQueue入门 聪明的你,一定会想知道更多。哈哈哈哈哈,下面主播就...
51 6
【源码】【Java并发】【LinkedBlockingQueue】适合中学体质的LinkedBlockingQueue入门
|
1月前
|
安全 Java
【源码】【Java并发】【ArrayBlockingQueue】适合中学者体质的ArrayBlockingQueue
前言 通过之前的学习是不是学的不过瘾,没关系,马上和主播来挑战源码的阅读 【Java并发】【ArrayBlockingQueue】适合初学体质的ArrayBlockingQueue入门 还有一件事
52 5
【源码】【Java并发】【ArrayBlockingQueue】适合中学者体质的ArrayBlockingQueue