解决ThreadLocal在开启子线程时,父线程向子线程值传递问题,源码分析

有关ThreadLocal的介绍我之前一篇文章已经做了介绍:https://blog.csdn.net/qq_26012495/article/details/86475725

本篇主要解决,在父线程中开启子线程时ThreadLocal存在的值传递问题,以及如何解决。

 

目录

 

一、ThreadLocal

1. 普通ThreadLocal存在的问题

二、InheritableThreadLocal 

1. InheritableThreadLocal源码分析

2. Thread类源码分析

 3. 修改为InheritableThreadLocal代码测试

4. InheritableThreadLocal在其他场景存在的问题

三、TransmittableThreadLocal(阿里开源)

1. 查看TransmittableThreadLocal源码

2. TtlExecutors类使用及源码分析


一、ThreadLocal

1. 普通ThreadLocal存在的问题

先使用普通的ThreadLocal类做一个测试,在main方法中通过new Thread方法来开启子线程,在子线程中打印父线程中set的ThreadLocal值。

public static void main(String[] args) throws Exception {

        ThreadLocal<String> threadLocal = new ThreadLocal<>();
        threadLocal.set("初始化的值能继承吗?");

        System.out.println("父线程的ThreadLocal值:" + threadLocal.get());
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("子线程到了");
                System.out.println("子线程的ThreadLocal值:" + threadLocal.get());
            }
        }).start();
    }

子线程打印的结果为null,说明父线程的ThreadLocal值在子线程中并未得到传递,而是中断了。 

二、InheritableThreadLocal 

1. InheritableThreadLocal源码分析

由于ThreadLocal父线程无法传递本地变量到子线程中,于是JDK引入了InheritableThreadLocal类,该类的首部描述:该类继承了ThreadLocal类,用以实现父线程到子线程的值继承;创建子线程时,子线程将接收父线程具有值的所有可继承线程局部变量的初始值。通常子线程的值与父线程相同;子线程的值可以被父线程重写的方法改变;

/**
 * This class extends <tt>ThreadLocal</tt> to provide inheritance of values
 * from parent thread to child thread: when a child thread is created, the
 * child receives initial values for all inheritable thread-local variables
 * for which the parent has values.  Normally the child's values will be
 * identical to the parent's; however, the child's value can be made an
 * arbitrary function of the parent's by overriding the <tt>childValue</tt>
 * method in this class.
 *
 * <p>Inheritable thread-local variables are used in preference to
 * ordinary thread-local variables when the per-thread-attribute being
 * maintained in the variable (e.g., User ID, Transaction ID) must be
 * automatically transmitted to any child threads that are created.
 *
 * @author  Josh Bloch and Doug Lea
 * @see     ThreadLocal
 * @since   1.2
 */

public class InheritableThreadLocal<T> extends ThreadLocal<T> {

共有如下三个方法,没有什么特殊的地方: 

public class InheritableThreadLocal<T> extends ThreadLocal<T> {
    /**
     * Computes the child's initial value for this inheritable thread-local
     * variable as a function of the parent's value at the time the child
     * thread is created.  This method is called from within the parent
     * thread before the child is started.
     * <p>
     * This method merely returns its input argument, and should be overridden
     * if a different behavior is desired.
     *
     * @param parentValue the parent thread's value
     * @return the child thread's initial value
     */
    protected T childValue(T parentValue) {
        return parentValue;
    }

    /**
     * Get the map associated with a ThreadLocal.
     *
     * @param t the current thread
     */
    ThreadLocalMap getMap(Thread t) {
       return t.inheritableThreadLocals;
    }

    /**
     * Create the map associated with a ThreadLocal.
     *
     * @param t the current thread
     * @param firstValue value for the initial entry of the table.
     */
    void createMap(Thread t, T firstValue) {
        t.inheritableThreadLocals = new ThreadLocalMap(this, firstValue);
    }
}

我们以最简单的创建线程为例

Thread thread = new Thread(...)

2. Thread类源码分析

通过查看Thread类源码,其有两个全局变量

ThreadLocal.ThreadLocalMap threadLocals = null;
ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;

Thread构造器,会调用init方法

public Thread(Runnable target) {
        init(null, target, "Thread-" + nextThreadNum(), 0);
    }

再看init方法,对inheritableThreadLocal变量进行了初始化,将当前线程的inheritableThreadLocal赋值给子线程的inheritableThreadLocal,完成了父线程到子线程的变量传递。

private void init(ThreadGroup g, Runnable target, String name,
                      long stackSize, AccessControlContext acc,
                      boolean inheritThreadLocals) {
        ...
        Thread parent = currentThread();
        if (inheritThreadLocals && parent.inheritableThreadLocals != null)
            this.inheritableThreadLocals =
                ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
        ...
    }

 3. 修改为InheritableThreadLocal代码测试

public static void main(String[] args) throws Exception {

        ThreadLocal<String> threadLocal = new InheritableThreadLocal<>();
        threadLocal.set("初始化的值能继承吗?");

        System.out.println("父线程的ThreadLocal值:" + threadLocal.get());
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("子线程到了");
                System.out.println("子线程的ThreadLocal值:" + threadLocal.get());
            }
        }).start();
    }

运行结果,可看到在new Thread创建子线程的场景中,子线程已经成功打印了父线程的ThreadLocal值。

4. InheritableThreadLocal在其他场景存在的问题

但是InheritableThreadLocal仍然存在一个问题,上述我们解决了在创建Thread时的传递,其传递时机是在创建线程时;但在线程池复用的情景中,没有创建Thread的触发条件。

举一个失败案例:创建一个固定大小为2的线程池,进行五次子线程执行,每次给父线程的threadLocal重新赋值,由于线程池大小为2,因此只有前两次触发初始化线程池,后面三次均为线程复用,理论上来说,子线程可继承的父线程变量到i=1截止。

public static void main(String[] args) throws Exception {
        ThreadLocal<String> threadLocal = new InheritableThreadLocal<>();

        ExecutorService threadPool = Executors.newFixedThreadPool(2);// 创建一个固定大小为2的线程池
        for (int i = 0; i < 5; i++) {
            threadLocal.set("初始化的值能继承吗?" + i);
            System.out.println("父线程的ThreadLocal值:" + threadLocal.get());
            threadPool.execute(new Runnable() {
                @Override
                public void run() {
                    System.out.println("子线程到了");
                    System.out.println("=========子线程的ThreadLocal值:" + threadLocal.get());
                }
            });
        }
    }

运行结果如我所料,子线程打印的值只有0和1

三、TransmittableThreadLocal(阿里开源)

使用线程池时,ThreadLocal父线程到子线程值传递问题需要TransmittableThreadLocal来解决。TransmittableThreadLocal是阿里开源的专门用以解决上述问题的类,使用时需要引入maven

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

TTL:https://github.com/alibaba/transmittable-thread-local#-%E9%9C%80%E6%B1%82%E5%9C%BA%E6%99%AF

1. 查看TransmittableThreadLocal源码

其继承了InheritableThreadLocal类,因此同样也支持new Thread时的值传递;

其中set和get方法均调用了addValue方法,addValue方法使用了全局的holder变量,将this指针put入holder中,this指的就是当前线程的ThreadLocal【因为ThreadLocal维护了线程和value的关系,不论是父线程还是子线程,都会被存入ThreadLocalMap中,因此此处的this包含了所有的父线程+子线程】

holder就是一个InheritableThreadLocal变量,该变量在子线程的执行过程中,缓存了所有线程的TransmittableThreadLocal(包括子线程+父线程),set方法缓存父线程,get方法缓存子线程(多数情况都是父线程赋值,子线程调用,所以不论是get还是set方法都要给holder存值)。

public class TransmittableThreadLocal<T> extends InheritableThreadLocal<T> {

    static InheritableThreadLocal<Map<TransmittableThreadLocal<?>, ?>> holder = new InheritableThreadLocal<Map<TransmittableThreadLocal<?>, ?>>() {
        protected Map<TransmittableThreadLocal<?>, ?> initialValue() {
            return new WeakHashMap();
        }

        protected Map<TransmittableThreadLocal<?>, ?> childValue(Map<TransmittableThreadLocal<?>, ?> parentValue) {
            return new WeakHashMap(parentValue);
        }
    };
    
    public final void set(T value) {
        super.set(value);
        if (null == value) {// 提供set null移除value的功能
            this.removeValue();
        } else {
            this.addValue();
        }

    }

    public final T get() {
        T value = super.get();
        if (null != value) {
            this.addValue();
        }

        return value;
    }

    void addValue() {
        if (!((Map)holder.get()).containsKey(this)) {
            ((Map)holder.get()).put(this, (Object)null);
        }
    }

}

2. TtlExecutors类使用及源码分析

上述问题在于线程池中线程复用没有触发条件,而只要在子线程执行时加入复制父线程的threadLocal即可。而原生的线程池创建方法不提供此功能,因此需要单独的线程池创建类支持,TtlExecutors,同样也是阿里开源的。

使用场景如下:

ExecutorService threadPool= TtlExecutors.getTtlExecutorService(Executors.newFixedThreadPool(poolSize));

修改刚才的代码,ThreadLocal和Executors修改为阿里的 TransmittableThreadLocal和TtlExecutors:

public static void main(String[] args) throws Exception {
        ThreadLocal<String> threadLocal = new TransmittableThreadLocal<>();

        ExecutorService threadPool = TtlExecutors.getTtlExecutorService(Executors.newFixedThreadPool(2));
        for (int i = 0; i < 5; i++) {
            threadLocal.set("初始化的值能继承吗?" + i);
            System.out.println("父线程的ThreadLocal值:" + threadLocal.get());
            threadPool.execute(new Runnable() {
                @Override
                public void run() {
                    System.out.println("子线程到了");
                    System.out.println("=========子线程的ThreadLocal值:" + threadLocal.get());
                }
            });
        }
    }

再来看TtlExecutors类,其getTtlExecutorService方法如下,返回的是ExecutorServiceTtlWrapper类

public static ExecutorService getTtlExecutorService(ExecutorService executorService) {
        return (ExecutorService)(executorService != null && !(executorService instanceof ExecutorServiceTtlWrapper) ? new ExecutorServiceTtlWrapper(executorService) : executorService);
    }

 再去看ExecutorServiceTtlWrapper,该类中未寻找到execute方法,因此在其父类ExecutorTtlWrapper中

class ExecutorServiceTtlWrapper extends ExecutorTtlWrapper implements ExecutorService {

execute方法中,执行Runnable时,外层包装了TtlRunnable类

class ExecutorTtlWrapper implements Executor {
    final Executor executor;

    public ExecutorTtlWrapper(Executor executor) {
        this.executor = executor;
    }

    public void execute(Runnable command) {
        this.executor.execute(TtlRunnable.get(command));
    }
}

看下TtlRunnable类的run方法(多线程的执行处,到达run方法时,子线程内部逻辑已经执行完毕,包括子线程的ThreadLocal的get或者set),发现以下4句是关键语句,正常run方法执行逻辑应该只有3,TtlRunnable的run实际上是在正常run的前后包装了逻辑:

  1. 一进入TtlRunnable就将当前的所有TransmittableThreadLocal存储在AtomicReference(copied)中
  2. 在子线程执行前,将copied(即1获取的)备份在backup中,copied中不存在的,holder中存在的,是刚刚加入的子线程threadLocal,把copied的全部remove掉,只留下该子线程的。
  3. 执行子线程(runnable的类是Runnable)
  4. 在子线程执行后,把刚刚执行的threadLocal清理掉,再把backup中的set回来,完成一次神操作。
public final class TtlRunnable implements Runnable {
    private final AtomicReference<Map<TransmittableThreadLocal<?>, Object>> copiedRef = new AtomicReference(TransmittableThreadLocal.copy());// 1
    
    public void run() {
        Map<TransmittableThreadLocal<?>, Object> copied = (Map)this.copiedRef.get();
        if (copied != null && (!this.releaseTtlValueReferenceAfterRun || this.copiedRef.compareAndSet(copied, (Object)null))) {
            Map backup = TransmittableThreadLocal.backupAndSetToCopied(copied);// 2

            try {
                this.runnable.run();// 3
            } finally {
                TransmittableThreadLocal.restoreBackup(backup);// 4
            }

        } else {
            throw new IllegalStateException("TTL value reference is released after run!");
        }
    }

以下是2和4的源码部分 

static Map<TransmittableThreadLocal<?>, Object> backupAndSetToCopied(Map<TransmittableThreadLocal<?>, Object> copied) {
        Map<TransmittableThreadLocal<?>, Object> backup = new HashMap();
        Iterator iterator = ((Map)holder.get()).entrySet().iterator();

        Entry entry;
        TransmittableThreadLocal threadLocal;

        while(iterator.hasNext()) {
            entry = (Entry)iterator.next();
            threadLocal = (TransmittableThreadLocal)entry.getKey();
            backup.put(threadLocal, threadLocal.get());// 执行备份
            if (!copied.containsKey(threadLocal)) {// 从holder中remove掉copied中不存在的
                iterator.remove();
                threadLocal.superRemove();
            }
        }

        iterator = copied.entrySet().iterator();

        while(iterator.hasNext()) {
            entry = (Entry)iterator.next();
            threadLocal = (TransmittableThreadLocal)entry.getKey();
            threadLocal.set(entry.getValue());
        }

        doExecuteCallback(true);
        return backup;
    }
static void restoreBackup(Map<TransmittableThreadLocal<?>, Object> backup) {
        doExecuteCallback(false);
        Iterator iterator = ((Map)holder.get()).entrySet().iterator();

        Entry entry;
        TransmittableThreadLocal threadLocal;
        // 把刚刚执行的那个清理掉,执行完不需要了,然后再把backup里面的拿回来,恢复初始状态
        while(iterator.hasNext()) {
            entry = (Entry)iterator.next();
            threadLocal = (TransmittableThreadLocal)entry.getKey();
            if (!backup.containsKey(threadLocal)) {
                iterator.remove();
                threadLocal.superRemove();
            }
        }

        iterator = backup.entrySet().iterator();

        while(iterator.hasNext()) {
            entry = (Entry)iterator.next();
            threadLocal = (TransmittableThreadLocal)entry.getKey();
            threadLocal.set(entry.getValue());
        }

    }

四、总结 

ThreadLocal

解决问题:

维护了线程和value的关系

存在问题:

无法解决父线程向子线程传递ThreadLocal值的问题

InheritableThreadLocal

解决问题:

JDK提供的,在通过new Thread创建子线程时,复制父线程的ThreadLocal值至子线程,触发开关为创建全新的子线程。

存在问题:

但线程池的线程复用机制打破了创建全新子线程的时机,导致子线程继承到错误的父线程ThreadLocal(还是复用前的子线程那个值)

TransmittableThreadLocal

解决问题:

阿里开源的TTL包,配合TtlExecutors、TtlRunnable、TtlCallable实现在子线程执行时的ThreadLocal值传递。

解决思路:

        ThreadLocal<String> threadLocal = new TransmittableThreadLocal<>();
        ExecutorService threadPool = TtlExecutors.getTtlExecutorService(Executors.newFixedThreadPool(2));
        for (int i = 0; i < 5; i++) {
            threadLocal.set("初始化的值能继承吗?" + i);
            System.out.println("父线程的ThreadLocal值:" + threadLocal.get());
            threadPool.execute(new Runnable() {// 初始化TtlRunnable
                @Override
                public void run() {
                    System.out.println("子线程到了");
                    System.out.println("=========子线程的ThreadLocal值:" + threadLocal.get());
                }
            });
        }

对照着刚才的示例代码 

  1. 在每一次调用threadLocal.set方法,都将当前的ThreadLocal存入holder的key中,值为null;
  2. threadPool.execute方法将Runnable包装成TtlRunnable,调用该方法时,还没进入子线程,完成TtlRunnable的初始化,TtlRunnable的初始化会将当前所有已经产生的ThreadLocal缓存;
  3. 未完待续
  • 8
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
在Java中,ThreadLocal是一种线程局部变量,它可以在多个线程中存储和访问数据,每个线程都有自己独立的副本。然而,ThreadLocal的数据在线程中默认是无法传递的。但是,可以通过一些特殊的ThreadLocal实现类来实现ThreadLocal数据在线程中的传递。 一种实现方式是使用InheritableThreadLocal类。InheritableThreadLocalThreadLocal的一个类,它允许线程继承线程ThreadLocal变量。当一个线程创建线程线程会自动拥有线程的InheritableThreadLocal变量的副本。这样,线程中设置的ThreadLocal变量的值可以在线程中访问到。 另一种实现方式是使用TransmittableThreadLocal类。TransmittableThreadLocal是一个第三方库,它提供了更强大的功能,可以在线程间传递ThreadLocal变量的值。它通过在线程切换保存和恢复ThreadLocal变量的值来实现传递。使用TransmittableThreadLocal,可以在线程中设置ThreadLocal变量的值,并在线程中访问到这个值。 下面是使用InheritableThreadLocal和TransmittableThreadLocal传递ThreadLocal变量到线程的示例代码: ```java // 使用InheritableThreadLocal传递ThreadLocal变量到线程 ThreadLocal<String> threadLocal = new InheritableThreadLocal<>(); threadLocal.set("Hello, World!"); Thread thread = new Thread(() -> { String value = threadLocal.get(); System.out.println("Value in child thread: " + value); }); thread.start(); // 使用TransmittableThreadLocal传递ThreadLocal变量到线程 ThreadLocal<String> threadLocal = new TransmittableThreadLocal<>(); threadLocal.set("Hello, World!"); Thread thread = new Thread(() -> { String value = threadLocal.get(); System.out.println("Value in child thread: " + value); }); thread.start(); ``` 在上面的代码中,我们首先创建了一个ThreadLocal变量,并在线程中设置了它的值。然后,我们创建了一个线程,并在线程中获取并打印了ThreadLocal变量的值。使用InheritableThreadLocal或TransmittableThreadLocal,我们可以在线程中成功访问到线程设置的ThreadLocal变量的值。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值