面试官:ThreadLocal解决线程之间值传递, InheritableThreadLocal解决父子线程值传递,线程池值如何传递呢

2,345 阅读3分钟

这里是weihubeats,觉得文章不错可以关注公众号小奏技术,文章首发。拒绝营销号,拒绝标题党

线程、父子线程、线程池

我们知道线程之间的值传递使用JDK自带的ThreadLocal即可解决,如果遇到需要父子线程值传递的场景也可以使用JDK提供的InheritableThreadLocal,但更多的业务场景实际是需要把任务提交给线程池时的ThreadLocal值传递到任务执行时

关于InheritableThreadLocal详细介绍说明请参考之前博文 weihubeats.blog.csdn.net/article/det…

InheritableThreadLocal简单测试

上面说的概念有点难懂,我们简单看看使用InheritableThreadLocal + 线程池的一个demo会有什么问题吧

public class InheritableThreadLocalTest {
    
    private static final AtomicInteger ID_SEQ = new AtomicInteger();
    private static final ExecutorService THREAD_POOL = Executors.newFixedThreadPool(1, r -> new Thread(r, "thread-" + ID_SEQ.getAndIncrement()));
    private static Integer i = 0;
    private static final ThreadLocal<String> inheritableThreadLocal = new InheritableThreadLocal<>();

    public static void main(String[] args) throws Exception {
        // 模拟接口调用
        IntStream.range(0, 10).forEach(InheritableThreadLocalTest::testInheritableThreadLocal);
        THREAD_POOL.shutdown();
    }

    public static void testInheritableThreadLocal(int s) {
        inheritableThreadLocal.set("小奏技术" + i++);
        Future<?> submit = THREAD_POOL.submit(new ZouTask("任务" + s));
        try {
            submit.get();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static class ZouTask implements Runnable{

        String taskName;

        public ZouTask(String taskName) {
            this.taskName = taskName;
        }

        @Override
        public void run() {
            System.out.println(taskName + "线程:" + Thread.currentThread().getName() + "获取到的值: " + inheritableThreadLocal.get());
            inheritableThreadLocal.remove();
        }
    }

运行结果: 在这里插入图片描述

可以看到我们在获取到第一次的值后就再也获取不到后面的InheritableThreadLocal的值了。正确的运行结果应该是如下的: 在这里插入图片描述 为什么会出现这种错误呢?原因很简单,InheritableThreadLocal只有在创建子线程的时候才会传递值。而线程池由于线程创建后会一直复用,所以导致后面获取不到值

TransmittableThreadLocal介绍

这里我们要实现线程池上线文的传递(值)就不得不引出我们今天的主角TransmittableThreadLocalTransmittableThreadLocal是阿里开源的线程池异步上线文传递的一个组件,并不是JDK自带的

github地址

功能介绍(官网介绍):

TransmittableThreadLocal(TTL):在使用线程池等会池化复用线程的执行组件情况下,提供ThreadLocal值的传递功能,解决异步执行时上下文传递的问题。一个Java标准库本应为框架/中间件设施开发提供的标配能力,本库功能聚焦 & 0依赖,支持Java 17/16/15/14/13/12/11/10/9/8/7/6。     JDK的InheritableThreadLocal类可以完成父线程到子线程的值传递。但对于使用线程池等会池化复用线程的执行组件的情况,线程由线程池创建好,并且线程是池化起来反复使用的;这时父子线程关系的ThreadLocal值传递已经没有意义,应用需要的实际上是把 任务提交给线程池时的ThreadLocal值传递到 任务执行时     整个TransmittableThreadLocal库的核心功能(用户API与框架/中间件的集成API、线程池ExecutorService/ForkJoinPool/TimerTask及其线程工厂的Wrapper),只有 ~1000 SLOC代码行,非常精小

TransmittableThreadLocal使用

引入依赖

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

我们还是基于上面那个例子来说明

public class TransmittableThreadLocalTest {


    private static final AtomicInteger ID_SEQ = new AtomicInteger();
    private static final ExecutorService EXECUTOR = Executors.newFixedThreadPool(1, r -> new Thread(r, "thread-" + ID_SEQ.getAndIncrement()));
    private static final TransmittableThreadLocal<String> ttl = new TransmittableThreadLocal<>();
    private static Integer i = 0;



    public static void main(String[] args) throws Exception {
        // 模拟接口调用
        IntStream.range(0, 10).forEach(TransmittableThreadLocalTest::testInheritableThreadLocal);
        EXECUTOR.shutdown();
    }


    public static void testInheritableThreadLocal(int s) {
        ttl.set("小奏技术" + i++);
        Future<?> submit = EXECUTOR.submit(TtlRunnable.get(new ZouTask("任务" + s)));
        try {
            submit.get();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
    }

    public static class ZouTask implements Runnable{

        String taskName;

        public ZouTask(String taskName) {
            this.taskName = taskName;
        }

        @Override
        public void run() {
            System.out.println(taskName + "线程:" + Thread.currentThread().getName() + "获取到的值: " + ttl.get());
            ttl.remove();
        }
    }
}

这里面有几个细节:

  1. TransmittableThreadLocal替换InheritableThreadLocal
  2. TtlRunnable增强JDK原始的Runnable接口

TransmittableThreadLocal使用方式大致如下。一些高级用法可能比如无侵入Agent接入等

TransmittableThreadLocal 在开源项目中的使用

TransmittableThreadLocal目前已经在很多开源项目中都有使用 在这里插入图片描述

感兴趣可以自己去看看

总结

TransmittableThreadLocal的使用大致就到这里。源码和原理相关的部分感兴趣可以去研究下,官网也有一些说明,这里就暂时不讲解了