虚拟线程与平台线程线程上下文传递问题

197 阅读2分钟

1. 准备工作

依赖注入

<dependencies>
    <!-- Spring Boot Starter -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <!-- TransmittableThreadLocal (TTL) -->
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>transmittable-thread-local</artifactId>
        <version>2.14.2</version>
    </dependency>
</dependencies>

定义线程池

@EnableAsync
@Configuration
public class CustomAsyncAutoConfiguration{

    @Bean("virtualThreadExecutor")
    public TaskExecutor virtualThreadExecutor() {
        ThreadFactory virtualThreadFactory = Thread.ofVirtual()
                .name("my-virtualThreadExecutor", 0)  // 前缀 + 起始编号
                .factory();

        return new TaskExecutorAdapter(Executors.newThreadPerTaskExecutor(virtualThreadFactory));
    }


    // 传统线程池执行器
    @Bean("threadPoolTaskExecutor")
    public TaskExecutor threadPoolTaskExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(10);
        executor.setMaxPoolSize(50);
        executor.setQueueCapacity(100);
        executor.setThreadNamePrefix("Async-Pool-threadPoolTaskExecutor");
        executor.initialize();
        return executor;
    }
}

定义上下文

public class TestContextHolder {
    private static final ThreadLocal<String> TEST = new ThreadLocal<>();

    public static void set(String value){
        TEST.set(value);
    }

    public static String get(){
        return TEST.get();
    }
}

controller

@RestController
public class TestController {

    @Autowired
    private AsyncService asyncService;

    @GetMapping("/test-io-task")
    public String testIoTask() {
        TestContextHolder.set("虚拟线程上下文");
        asyncService.doIoBoundTask();  // 使用虚拟线程
        return "IO任务已提交(虚拟线程)";
    }

    @GetMapping("/test-cpu-task")
    public String testCpuTask() {
        TestContextHolder.set("传统线程池上下文");
        asyncService.doCpuBoundTask();  // 使用传统线程池
        return "CPU任务已提交(线程池)";
    }

    @GetMapping("/test-cpu-task1")
    public String testCpuTask1() {
        TestContextHolder.set("默认线程池上下文");
        asyncService.doCpuBoundTask1();  // 使用默认线程池
        return "CPU任务已提交(线程池)";
    }
}

service

@Service
public class AsyncService {
    // 使用虚拟线程执行(适合IO密集型任务)
    @Async("virtualThreadExecutor")
    public void doIoBoundTask() {

        System.out.println("IO任务运行在: " + Thread.currentThread()+":"+TestContextHolder.get());
    }

    // 使用传统线程池执行(适合CPU密集型任务)
    @Async("threadPoolTaskExecutor")
    public void doCpuBoundTask() {
        System.out.println("CPU任务运行在: " + Thread.currentThread()+":"+TestContextHolder.get());
    }

    @Async
    public void doCpuBoundTask1() {
        System.out.println("默认线程池的CPU任务运行在: " + Thread.currentThread()+":"+TestContextHolder.get());
    }
}

测试结果

CPU任务运行在: Thread[#79,Async-Pool-threadPoolTaskExecutor1,5,main]:null
IO任务运行在: VirtualThread[#80,my-virtualThreadExecutor0]/runnable@ForkJoinPool-1-worker-1:null
默认线程池的CPU任务运行在: Thread[#83,SimpleAsyncTaskExecutor-1,5,main]:null

看结果可知TestContextHolder.get()获取的值为null、说明ThreadLocal类不支持上下文传递

我们现在更改TestContextHolder类的ThreadLocal的实现类为TransmittableThreadLocal

private static final ThreadLocal<String> TEST = new TransmittableThreadLocal<>();

再次执行测试 结果为:

默认线程池的CPU任务运行在: Thread[#81,SimpleAsyncTaskExecutor-1,5,main]:默认线程池 传统线程池上下文
IO任务运行在: VirtualThread[#82,my-virtualThreadExecutor0]/runnable@ForkJoinPool-1-worker-1:虚拟线程上下文
CPU任务运行在: Thread[#85,Async-Pool-threadPoolTaskExecutor1,5,main]:传统线程池上下文