美文网首页
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