【源码】【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.

目录
相关文章
|
4月前
|
JSON 网络协议 安全
【Java】(10)进程与线程的关系、Tread类;讲解基本线程安全、网络编程内容;JSON序列化与反序列化
几乎所有的操作系统都支持进程的概念,进程是处于运行过程中的程序,并且具有一定的独立功能,进程是系统进行资源分配和调度的一个独立单位一般而言,进程包含如下三个特征。独立性动态性并发性。
261 1
|
4月前
|
JSON 网络协议 安全
【Java基础】(1)进程与线程的关系、Tread类;讲解基本线程安全、网络编程内容;JSON序列化与反序列化
几乎所有的操作系统都支持进程的概念,进程是处于运行过程中的程序,并且具有一定的独立功能,进程是系统进行资源分配和调度的一个独立单位一般而言,进程包含如下三个特征。独立性动态性并发性。
269 1
|
5月前
|
数据采集 存储 弹性计算
高并发Java爬虫的瓶颈分析与动态线程优化方案
高并发Java爬虫的瓶颈分析与动态线程优化方案
|
5月前
|
存储 小程序 Java
热门小程序源码合集:微信抖音小程序源码支持PHP/Java/uni-app完整项目实践指南
小程序已成为企业获客与开发者创业的重要载体。本文详解PHP、Java、uni-app三大技术栈在电商、工具、服务类小程序中的源码应用,提供从开发到部署的全流程指南,并分享选型避坑与商业化落地策略,助力开发者高效构建稳定可扩展项目。
Java 数据库 Spring
232 0
|
5月前
|
算法 Java
Java多线程编程:实现线程间数据共享机制
以上就是Java中几种主要处理多线程序列化资源以及协调各自独立运行但需相互配合以完成任务threads 的技术手段与策略。正确应用上述技术将大大增强你程序稳定性与效率同时也降低bug出现率因此深刻理解每项技术背后理论至关重要.
428 16
|
6月前
|
缓存 并行计算 安全
关于Java多线程详解
本文深入讲解Java多线程编程,涵盖基础概念、线程创建与管理、同步机制、并发工具类、线程池、线程安全集合、实战案例及常见问题解决方案,助你掌握高性能并发编程技巧,应对多线程开发中的挑战。
|
6月前
|
数据采集 存储 前端开发
Java爬虫性能优化:多线程抓取JSP动态数据实践
Java爬虫性能优化:多线程抓取JSP动态数据实践
|
7月前
|
Java API 调度
从阻塞到畅通:Java虚拟线程开启并发新纪元
从阻塞到畅通:Java虚拟线程开启并发新纪元
409 83
|
7月前
|
安全 算法 Java
Java 多线程:线程安全与同步控制的深度解析
本文介绍了 Java 多线程开发的关键技术,涵盖线程的创建与启动、线程安全问题及其解决方案,包括 synchronized 关键字、原子类和线程间通信机制。通过示例代码讲解了多线程编程中的常见问题与优化方法,帮助开发者提升程序性能与稳定性。
340 0

热门文章

最新文章