美文网首页
JAVA进阶篇(8)—TransmittableThreadLo

JAVA进阶篇(8)—TransmittableThreadLo

作者: 小胖学编程 | 来源:发表于2020-09-10 17:32 被阅读0次

    1. 传统的ThreadLocal

    ThreadLocal是线程本地变量。即每一个线程中都会存在一个ThreadLocal对象。

    ThreadLocal.set()方法:获取当前线程中的ThreadLocalMap对象,将ThreadLocal对象作为key,set()的值作为value,放入到线程中ThreadLocalMap中。

    问题:若是在线程中开启子线程,那么子线程也会存在一个ThreadLocalMap对象,但是它不存在父线程ThreadLocalMap对象中的值。

    @Slf4j
    public class testThreadLocal {
    
        @Test
        public void testThreadLocal2() throws InterruptedException {
    
            ThreadLocal<String> local = new ThreadLocal<>();
            try {
                local.set("我是主线程");
                ExecutorService executorService = Executors.newFixedThreadPool(1);
    
                CountDownLatch c1 = new CountDownLatch(1);
                CountDownLatch c2 = new CountDownLatch(1);
    
                executorService.execute(() -> {
                    System.out.println("线程1" + local.get());
                    c1.countDown();
                });
                c1.await();
                executorService.execute(() -> {
                    System.out.println("线程2" + local.get());
                    c2.countDown();
                });
                c2.await();
                executorService.shutdownNow();
            } finally {
                //使用完毕,清除线程中ThreadLocalMap中的key。
                local.remove();
            }
        }
    }
    

    执行结果:

    线程1null
    线程2null
    

    2. 可继承的InheritableThreadLocal

    image.png

    InheritableThreadLocal是ThreadLocal的子类,字面意义上是子线程可以继承父线程中的值。

    案例1:

    @Slf4j
    public class testThreadLocal {
        @Test
        public void testInheritableThreadLocal() throws InterruptedException {
            ThreadLocal<String> local = new InheritableThreadLocal<>();
            local.set("我是主线程");
            new Thread(() -> {
                System.out.println("子线程1" + local.get());
            }).start();
            Thread.sleep(2000);
        }
    }
    

    执行结果:

    子线程1我是主线程
    

    可以看到,子线程ThreadLocalMap也持有父线程ThreadLocalMap的值。

    源码简介:

    public class InheritableThreadLocal<T> extends ThreadLocal<T> {
       ...
        /**
         * 创建Map的时候,会初始化当前线程中的inheritableThreadLocals变量。
         *
         * @param t 当前的线程。
         * @param firstValue 初始化table的第一个值。
         */
        void createMap(Thread t, T firstValue) {
            t.inheritableThreadLocals = new ThreadLocalMap(this, firstValue);
        }
    }
    
    public
    class Thread implements Runnable {
        /*
         * 与此线程相关的InheritableThreadLocal值。这map是由InheritableThreadLocal类维护。
         */
        ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
    
        //线程初始化
        private void init(ThreadGroup g, Runnable target, String name,
                          long stackSize, AccessControlContext acc,
                          boolean inheritThreadLocals) {
            ...
            //如果inheritThreadLocals为true且父类存在inheritableThreadLocals,那么
            //子类将继承父类的inheritableThreadLocals的值。
            if (inheritThreadLocals && parent.inheritableThreadLocals != null)
                this.inheritableThreadLocals =
                    ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
            /* Stash the specified stack size in case the VM cares */
            this.stackSize = stackSize;
    
            /* Set thread ID */
            tid = nextThreadID();
        }
    }
    

    由源码可知,线程只有在初始化的时候才会继承父类ThreadLocal的值。也就是若是线程池的话,以及子类依旧不能继承父类的ThreadLocal的值。

    实验:

    @Slf4j
    public class testThreadLocal {
    
        @Test
        public void testThreadLocal2() throws InterruptedException {
    
            ThreadLocal<String> local = new InheritableThreadLocal<>();
            try {
                local.set("我是主线程");
                ExecutorService executorService = Executors.newFixedThreadPool(1);
    
                CountDownLatch c1 = new CountDownLatch(1);
                CountDownLatch c2 = new CountDownLatch(1);
                //初始化init的时候,赋予了父线程的ThreadLocal的值
                executorService.execute(() -> {
                    System.out.println("线程1" + local.get());
                    c1.countDown();
                });
                c1.await();
                //主线程修改值
                local.set("修改主线程");
                //再次调用,查看效果
                executorService.execute(() -> {
                    System.out.println("线程2" + local.get());
                    c2.countDown();
                });
                c2.await();
                executorService.shutdownNow();
            } finally {
                //使用完毕,清除线程中ThreadLocalMap中的key。
                local.remove();
            }
        }
    }
    

    执行结果:

    线程1我是主线程
    线程2我是主线程
    

    可以看到,线程池只会初始化一次,依旧存在问题。

    3. 最终解决方案TransmittableThreadLocal

    阿里巴巴开源解决方案:https://github.com/alibaba/transmittable-thread-local

    <dependency>
         <groupId>com.alibaba</groupId>
         <artifactId>transmittable-thread-local</artifactId>
         <version>2.11.5</version>
         <scope>compile</scope>
    </dependency>
    

    3.1 业务侵入式

    alibaba提供了一系列的包装类,可以对ExecutorServic及其子类进行装饰,由TtlExecutors类实现。

        @Test
        public void testInheritableThreadLocal3() throws InterruptedException {
            TransmittableThreadLocal<String> local = new TransmittableThreadLocal<>();
            local.set("我是主线程");
            //生成额外的代理
    
            ExecutorService executorService = Executors.newFixedThreadPool(1);
            //核心装饰代码
            executorService = TtlExecutors.getTtlExecutorService(executorService);
    
            CountDownLatch c1 = new CountDownLatch(1);
            CountDownLatch c2 = new CountDownLatch(1);
    
            executorService.submit(() -> {
                System.out.println("我是线程1:" + local.get());
                c1.countDown();
            });
            c1.await();
            local.set("修改主线程");
            System.out.println(local.get());
            executorService.submit(() -> {
                System.out.println("我是线程2:" + local.get());
                c2.countDown();
            });
            c2.await();
        }
    

    3.2 agent实现

    需配置启动参数-javaagent:path/to/transmittable-thread-local-2.x.x.jar

        @Test
        public void testTTL() throws InterruptedException {
            TransmittableThreadLocal<String> local = new TransmittableThreadLocal<>();
            local.set("我是主线程");
            //生成额外的代理
    
            CountDownLatch c1 = new CountDownLatch(1);
            CountDownLatch c2 = new CountDownLatch(1);
    
            CompletableFuture.supplyAsync(() -> {
                System.out.println("开启了线程1:" + local.get());
                c1.countDown();
                return "线程1的值";
            });
            c1.await();
            local.set("修改主线程");
            System.out.println(local.get());
    
            CompletableFuture.supplyAsync(() -> {
                System.out.println("开启了线程2:" + local.get());
                c1.countDown();
                return "线程2的值";
            });
            c2.await();
            System.out.println("我是主线程:" + local.get());
        }
    

    执行结果:

    开启了线程1:我是主线程
    修改主线程
    开启了线程2:修改主线程
    
    idea修改配置.png

    在ThreadLocal的需求场景即是TransmittableThreadLocal的潜在需求场景,如果你的业务需要『在使用线程池等会池化复用线程的执行组件情况下传递ThreadLocal』则是TransmittableThreadLocal目标场景。

    下面是几个典型场景例子。

    分布式跟踪系统 或 全链路压测(即链路打标)
    日志收集记录系统上下文
    Session级Cache
    应用容器或上层框架跨应用代码给下层SDK传递信息

    JAVA并发(4)— ThreadLocal源码角度分析是否真正能造成内存溢出!

    推荐阅读

    https://www.cnblogs.com/Nonnetta/p/10175662.html

    相关文章

      网友评论

          本文标题:JAVA进阶篇(8)—TransmittableThreadLo

          本文链接:https://www.haomeiwen.com/subject/ybvpektx.html