Java随笔3-TransmittableThreadLocal

本文通过一个测试案例展示了在Java中,InheritableThreadLocal在线程池环境下无法有效传递变量,而TransmittableThreadLocal能够解决这一问题。TransmittableThreadLocal通过在创建Runnable时捕获主线程环境并在执行时恢复,确保了线程间变量的正确传递。文章还简单介绍了TransmittableThreadLocal的工作原理,并强调了其在多线程和线程池场景下的重要性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

阅读本文前,需要了解

① ThreadLocal的特性,参考:Java随笔1-ThreadLocal

② InheritableThreadLocal的特性,参考:Java随笔2-InheritableThreadLocal

③ transmittable:adj. 可传送的,可传播的

④ TransmittableThreadLocal 是Alibaba开源的,需配合TtlRunnableTtlCallable 使用

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

1、特性

InheritableThreadLocal解决了父子线程之间变量传递的问题,为什么InheritableThreadLocal能解决,随笔2中分析了是new Thread时,子线程拷贝了父线程的inheritableThreadLocals属性。

多线程场景下,总是会创建新线程吗?答案是no!因为线程池,先把线程创建完,随用随取。

基于线程池的多线程执行任务,根据原理似乎InheritableThreadLocal不能保证父子线程之间变量的正确传递,是吗?那TransmittableThreadLocal呢?且看以下demo:

import com.alibaba.ttl.TransmittableThreadLocal;
import com.alibaba.ttl.TtlRunnable;
import org.junit.Test;

import java.util.concurrent.*;

public class TransmittableTLTest {

    InheritableThreadLocal<String> inheritableTL = new InheritableThreadLocal<>();

    TransmittableThreadLocal<String> transmittableTL = new TransmittableThreadLocal<>();
    
    ExecutorService pool = new ThreadPoolExecutor(5, 5, 1, TimeUnit.SECONDS, new LinkedBlockingQueue<>());

    @Test
    public void compare() throws InterruptedException {

        inheritableTL.set("祖先");
        CountDownLatch latch1 = new CountDownLatch(10);
        for (int i = 1; i <= 10; i++) {
            int index = i;
            pool.submit(() -> {
                System.out.println("inheritableTL, 儿子线程" + Thread.currentThread().getName() + "取出:" + inheritableTL.get());
                inheritableTL.set("儿子" + index);
                latch1.countDown();
            });
        }
        latch1.await();
        System.out.println("inheritableTL, 主线程取出:" + inheritableTL.get() + "\n");

        transmittableTL.set("祖先");
        CountDownLatch latch2 = new CountDownLatch(10);
        for (int i = 1; i <= 10; i++) {
            int index = i;
            Runnable runnable = TtlRunnable.get(() -> {
                System.out.println("transmittableTL, " + index + " 儿子线程取出:" + transmittableTL.get());
                transmittableTL.set("儿子" + index);
                latch2.countDown();
            });
            pool.submit(runnable);
        }
        latch2.await();
        System.out.println("transmittableTL, 主线程取出:" + transmittableTL.get());
    }
}

执行结果:

inheritableTL, 儿子线程pool-1-thread-1取出:祖先
inheritableTL, 儿子线程pool-1-thread-2取出:祖先
inheritableTL, 儿子线程pool-1-thread-3取出:祖先
inheritableTL, 儿子线程pool-1-thread-4取出:祖先
inheritableTL, 儿子线程pool-1-thread-5取出:祖先
inheritableTL, 儿子线程pool-1-thread-5取出:儿子5
inheritableTL, 儿子线程pool-1-thread-2取出:儿子2
inheritableTL, 儿子线程pool-1-thread-1取出:儿子1
inheritableTL, 儿子线程pool-1-thread-3取出:儿子3
inheritableTL, 儿子线程pool-1-thread-5取出:儿子6
inheritableTL, 主线程取出:祖先

transmittableTL, 3 儿子线程取出:祖先
transmittableTL, 5 儿子线程取出:祖先
transmittableTL, 1 儿子线程取出:祖先
transmittableTL, 6 儿子线程取出:祖先
transmittableTL, 4 儿子线程取出:祖先
transmittableTL, 2 儿子线程取出:祖先
transmittableTL, 10 儿子线程取出:祖先
transmittableTL, 9 儿子线程取出:祖先
transmittableTL, 8 儿子线程取出:祖先
transmittableTL, 7 儿子线程取出:祖先
transmittableTL, 主线程取出:祖先

注意,线程池初始设定了coreSize = maxSize = 5。

不出所料,inheritablTL在前5次,还能取出主线程设定的“祖先”,后面开始乱套了,取到的是该线程上一次执行时设定的“儿子x”信息。

不负所望,transmittableTL在10次执行中取出的一直是主线程设定的信息。

总结:TransmittableThreadLocal是用于解决 “在使用线程池等会缓存线程的组件情况下传递ThreadLocal” 问题的 InheritableThreadLocal 扩展。

2、原理

InheritableThreadLocal解决父子线程间变量传递,主要是new Thread时的拷贝;类似,TransmittableThreadLocal解决父线程与线程池线程间变量传递,主要是创建Runnable时,捕获当时的主线程环境,在执行任务时,把环境回放到当前线程池线程上;任务执行完,恢复环境。一般的Runnable显然是不支持的,所以为了配合TransmittableThreadLocal,需要使用对应的TtlRunnable。

以demo为例,下图描述了线程池中某一线程初次执行的流程:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值