如何在子线程和线程池中使用 ThreadLocal 传输上下文

问题举例

  • 在 Spring 框架中,使用 @Async 注解时,如何获取 ThreadLocal 中的数据?
  • 使用 CompletableFuture.supplyAsync 处理异步中,supplyAsync执行的方法如何获取 ThreadLocal 中的数据?
  • Executor 线程池中,如何获取 ThreadLocal 中的数据?

问题解决

Spring 框架 @Async

  • 没有配置线程池,每执行一次都会创建新的线程处理,只需要使用 InheritableThreadLocal 即可获取。
public final class ThreadContext {
    private static final ThreadLocal<Long> USER_ID_LOCAL = new InheritableThreadLocal<>();

    public static Long getUserId() {
        return USER_ID_LOCAL.get();
    }

    public static void setUserId(Long userId) {
        USER_ID_LOCAL.set(userId);
    }

    public static void clear() {
        USER_ID_LOCAL.remove();
    }
}

public class BusinessTask {

    @Async
    public void handle() {
        System.out.println(ThreadContext.getUserId());
    }
}
  • 配置线程池,每次执行都会由线程池分配线程,使用 JDK 提供的 InheritableThreadLocal 无法获取到数据,而需要使用 Alibaba 扩展 InheritableThreadLocal 的 TransmittableThreadLocal。

maven pom 配置:

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

这只是配置线程池方式之一,这里不阐述太多,只是举例说明使用:

public class AsyncThreadPoolConfiguration implements AsyncConfigurer {

    @Override
    public Executor getAsyncExecutor() {
        ThreadPoolTaskExecutor threadPool = new ThreadPoolTaskExecutor();
        // TODO 配置具体参数
        threadPool.initialize();

        // 重点:使用 TTL 提供的 TtlExecutors
        return TtlExecutors.getTtlExecutor(threadPool);
    }

    @Override
    public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
        return new SimpleAsyncUncaughtExceptionHandler();
    }
}

public final class ThreadContext {
    // 只需替换 InheritableThreadLocal 为 TransmittableThreadLocal 即可
    private static final ThreadLocal<Long> USER_ID_LOCAL = new TransmittableThreadLocal<>();

    public static Long getUserId() {
        return USER_ID_LOCAL.get();
    }

    public static void setUserId(Long userId) {
        USER_ID_LOCAL.set(userId);
    }

    public static void clear() {
        USER_ID_LOCAL.remove();
    }
}

public class BusinessTask {

    @Async
    public void handle() {
        System.out.println(ThreadContext.getUserId());
    }
}
  • 把 InheritableThreadLocal 替换为 TTL 提供的 TransmittableThreadLocal
  • 使用 TTL 提供的 TtlExecutors 包装线程池对象

通过解决了 Spring @Async 注解的问题,即可举一反三,CompletableFuture.supplyAsync 和 Executor 亦可以在这两种方法处理。
线程池中传输必须配合 TransmittableThreadLocal 和 TtlExecutors 使用。

PS:
对代码不明白地方可联系讨论或对写的不好的地方望不吝指教。

推荐阅读更多精彩内容