[ThreadLocal系列]简单例子了解ThreadLocal/InheritableThreadLocal/TransmittableThreadLocal

479 阅读4分钟

ThreadLocal和InheritableThreadLocal

  • ThreadLocal是线程上下文,但是当前线程如果创建子线程,那么子线程无法获取到值
// console:
// 当前线程变量:1
// 当前线程变量:null
public class Test {
    static ThreadLocal<Integer> threadLocal = new ThreadLocal<>();
    public static void main(String[] args) {
        threadLocal.set(1);
        System.out.println("当前线程变量:" + threadLocal.get());
        new Thread(() -> System.out.println("当前线程变量:" + threadLocal.get())).start();
    }
}
  • 这种机制本身没有错误,线程里创建了新线程,本来就是不一样的上下文。那如果我们需要让子线程也继承上下文你的值呢?InheritableThreadLocal派上用场
// console:
// 当前线程变量:1
// 当前线程变量:1
public class Test {
    static InheritableThreadLocal<Integer> threadLocal = new InheritableThreadLocal<>();
    public static void main(String[] args) {
        threadLocal.set(1);
        System.out.println("当前线程变量:" + threadLocal.get());
        new Thread(() -> System.out.println("当前线程变量:" + threadLocal.get())).start();
    }
}
  • 但是InheritableThreadLocal还是无法满足所有场景,比如线程池的情况,里面的线程可是复用的,那个上下文会被不断重复使用,这不是我们需要的。那么能解决这种问题的就是:transmittable-thread-local

transmittable-thread-local

<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>transmittable-thread-local</artifactId>
    <version>2.12.3</version>
</dependency>
  • 实现2个例子
// 直接创建子线程查看复用效果
static TransmittableThreadLocal<Integer> threadLocal = new TransmittableThreadLocal<>();
  public static void main(String[] args) {
      example1();
      example2();
  }
  public static void example1() {
      threadLocal.set(1);
      System.out.println("当前线程变量:" + threadLocal.get());
      new Thread(() -> System.out.println("当前线程变量:" + threadLocal.get())).start();
  }
}
//线程池提交任务看看上下文传递效果
public static void example2() {
    int count = 10;
    ExecutorService executorService = TtlExecutors.getTtlExecutorService(Executors.newFixedThreadPool(2));
    while (count-- > 0) {
        threadLocal.set(count);
        System.out.println("当前线程变量:" + threadLocal.get());
        executorService.submit(new Runnable() {
            @Override
            public void run() {
                System.out.println("线程id:" + Thread.currentThread().getId() + ",线程池-线程变量:" + threadLocal.get());
            }
        });
    }
}
  • 尝试在spring异步执行里引入ttl
    • maven依赖
 <dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-dependencies</artifactId>
            <version>2.6.1</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter</artifactId>
    </dependency>
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>transmittable-thread-local</artifactId>
        <version>2.12.3</version>
    </dependency>
    <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-api</artifactId>
    </dependency>
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
    </dependency>
</dependencies>
// 先构造一个不引入ttl构造的线程池的异步任务
// 异步任务类
@Slf4j
@Component
public class AsyncTask {
    @Async
    public void asyncRun() {
        log.info("asyncRun: 线程id:{},当前trace_id:{}", Thread.currentThread().getId(), App.threadLocal.get());
    }
}
// 异步线程配置类
@Configuration
public class AsyncConfig implements AsyncConfigurer {
    @Override
    public Executor getAsyncExecutor() {
        // 给定的线程数小点容易观察
        return new ThreadPoolExecutor(2, 2, 60L, TimeUnit.SECONDS, new ArrayBlockingQueue<>(100));
    }
}
// 启动类
@SpringBootApplication
@EnableScheduling
@EnableAsync
@Slf4j
public class App {
    public static TransmittableThreadLocal<String> threadLocal = new TransmittableThreadLocal<>();
    @Autowired
    AsyncTask asyncTask;
    public static void main(String[] args) {
        SpringApplication.run(App.class, args);
    }
    @Scheduled(fixedRate = 2000L)
    public void schedule() {
        String traceId = UUID.randomUUID().toString();
        threadLocal.set(traceId);
        log.info("定时任务线程id:{},当前trace_id:{}", Thread.currentThread().getId(), threadLocal.get());
        asyncTask.asyncRun();
    }
}
// 以下是console输出(可以发现两条线程的traceid一直维持最初设置的值了)
2021-12-25 15:56:59.402  INFO 15480 --- [   scheduling-1] org.example.App                          : 定时任务线程id:24,当前trace_id:5af5e4ac-b911-45d4-8554-3a4f4ad13aa4
2021-12-25 15:56:59.407  INFO 15480 --- [           main] org.example.App                          : Started App in 1.454 seconds (JVM running for 2.926)
2021-12-25 15:56:59.471  INFO 15480 --- [pool-1-thread-1] org.example.aysnc.AsyncTask              : asyncRun: 线程id:25,当前trace_id:5af5e4ac-b911-45d4-8554-3a4f4ad13aa4
2021-12-25 15:57:01.416  INFO 15480 --- [   scheduling-1] org.example.App                          : 定时任务线程id:24,当前trace_id:f40a801a-45a0-4ec9-9c7a-97d2536bdaac
2021-12-25 15:57:01.417  INFO 15480 --- [pool-1-thread-2] org.example.aysnc.AsyncTask              : asyncRun: 线程id:32,当前trace_id:f40a801a-45a0-4ec9-9c7a-97d2536bdaac
2021-12-25 15:57:03.411  INFO 15480 --- [   scheduling-1] org.example.App                          : 定时任务线程id:24,当前trace_id:77ea9d4f-3848-4316-bb6e-0216a43e5ff7
2021-12-25 15:57:03.411  INFO 15480 --- [pool-1-thread-1] org.example.aysnc.AsyncTask              : asyncRun: 线程id:25,当前trace_id:5af5e4ac-b911-45d4-8554-3a4f4ad13aa4
2021-12-25 15:57:05.413  INFO 15480 --- [   scheduling-1] org.example.App                          : 定时任务线程id:24,当前trace_id:4e439446-084b-4e41-812b-bf545131abfd
2021-12-25 15:57:05.413  INFO 15480 --- [pool-1-thread-2] org.example.aysnc.AsyncTask              : asyncRun: 线程id:32,当前trace_id:f40a801a-45a0-4ec9-9c7a-97d2536bdaac
2021-12-25 15:57:07.410  INFO 15480 --- [   scheduling-1] org.example.App                          : 定时任务线程id:24,当前trace_id:0ce9642d-048a-4e51-9cb3-5101a19ddae0
2021-12-25 15:57:07.410  INFO 15480 --- [pool-1-thread-1] org.example.aysnc.AsyncTask              : asyncRun: 线程id:25,当前trace_id:5af5e4ac-b911-45d4-8554-3a4f4ad13aa4
2021-12-25 15:57:09.409  INFO 15480 --- [   scheduling-1] org.example.App                          : 定时任务线程id:24,当前trace_id:df5f8369-9421-4034-9f76-1dbe3f8d6e21
2021-12-25 15:57:09.410  INFO 15480 --- [pool-1-thread-2] org.example.aysnc.AsyncTask              : asyncRun: 线程id:32,当前trace_id:f40a801a-45a0-4ec9-9c7a-97d2536bdaac
//现在用ttl修饰一下线程池
@Configuration
public class AsyncConfig implements AsyncConfigurer {
    @Override
    public Executor getAsyncExecutor() {
        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(2, 2, 60L, TimeUnit.SECONDS, new ArrayBlockingQueue<>(100));
        return TtlExecutors.getTtlExecutor(threadPoolExecutor);
    }
}
//console输出如下
2021-12-25 16:01:17.841  INFO 4428 --- [   scheduling-1] org.example.App                          : 定时任务线程id:24,当前trace_id:8b17bd65-edb5-4d10-b630-15ba89764ff8
2021-12-25 16:01:17.844  INFO 4428 --- [           main] org.example.App                          : Started App in 1.45 seconds (JVM running for 2.875)
2021-12-25 16:01:17.917  INFO 4428 --- [pool-1-thread-1] org.example.aysnc.AsyncTask              : asyncRun: 线程id:26,当前trace_id:8b17bd65-edb5-4d10-b630-15ba89764ff8
2021-12-25 16:01:19.854  INFO 4428 --- [   scheduling-1] org.example.App                          : 定时任务线程id:24,当前trace_id:df551e9f-0eec-432b-af92-009b8032b4db
2021-12-25 16:01:19.855  INFO 4428 --- [pool-1-thread-2] org.example.aysnc.AsyncTask              : asyncRun: 线程id:32,当前trace_id:df551e9f-0eec-432b-af92-009b8032b4db
2021-12-25 16:01:21.854  INFO 4428 --- [   scheduling-1] org.example.App                          : 定时任务线程id:24,当前trace_id:cac54b9b-c1ef-46d4-8c89-422316bc59f7
2021-12-25 16:01:21.854  INFO 4428 --- [pool-1-thread-1] org.example.aysnc.AsyncTask              : asyncRun: 线程id:26,当前trace_id:cac54b9b-c1ef-46d4-8c89-422316bc59f7
2021-12-25 16:01:23.853  INFO 4428 --- [   scheduling-1] org.example.App                          : 定时任务线程id:24,当前trace_id:69875240-9745-41b5-831b-66ddfbca2ea7
2021-12-25 16:01:23.853  INFO 4428 --- [pool-1-thread-2] org.example.aysnc.AsyncTask              : asyncRun: 线程id:32,当前trace_id:69875240-9745-41b5-831b-66ddfbca2ea7
2021-12-25 16:01:25.848  INFO 4428 --- [   scheduling-1] org.example.App                          : 定时任务线程id:24,当前trace_id:b81db337-818d-40ed-929b-971ce460d5c2
2021-12-25 16:01:25.848  INFO 4428 --- [pool-1-thread-1] org.example.aysnc.AsyncTask              : asyncRun: 线程id:26,当前trace_id:b81db337-818d-40ed-929b-971ce460d5c2
2021-12-25 16:01:27.846  INFO 4428 --- [   scheduling-1] org.example.App                          : 定时任务线程id:24,当前trace_id:c59338a3-d981-4ca2-a8a9-7ba6815165cb
2021-12-25 16:01:27.847  INFO 4428 --- [pool-1-thread-2] org.example.aysnc.AsyncTask              : asyncRun: 线程id:32,当前trace_id:c59338a3-d981-4ca2-a8a9-7ba6815165cb

结尾

  • 简单对比了几个ThreadLocal的区别,下一篇文章会对其工作原理做解析
  • 大家有什么问题可以留言,如果觉得我写得可以,请给我一个赞!谢谢,你们的支持是我不断更新的动力,我还有其他专栏大家可以看看(xxl-job,feign等),后续还会更新各种框架的使用和原理解析!