线程池任务装饰器

1,748 阅读2分钟

这是我参与2022首次更文挑战的第7天,活动详情查看:2022首次更文挑战

一、背景

​ 若使用过线程池进行任务处理过就会知道,如果业务线程将具体的执行任务提交到线程池中的线程进行处理,那么如果具体的任务执行需要获取到原业务线程的上下文信息,这时该如何处理?总不能每次手动将原业务线程中的上下文信息作为调用参数,传递给线程池的线程中。在阅读线程池相关信息后发现,线程池中提供了一个装饰器的功能,刚好可以简单有效的处理这一场景。

二、正文

创建线程池装饰器步骤:

  1. 创建一个类继承于TaskDecorator接口,并实现它的decorate方法,返回原任务的包装任务。上下文复制装饰器,将上下文传递给线程池的每一个线程任务。 ①:在线程内获取父线程的上下文信息。
    ②:将获取到的父线程上下文信息赋值到子线程。
    ③:线程执行完成之后,清理线程的上下文信息。
public class ContextCopyingTaskDecorator implements TaskDecorator {
    @Override
    public Runnable decorate(Runnable runnable) {
        //①
        Map<String, Object> context = ThreadContextHandler.getThreadLocal();
        return () -> {
            try {
                //②
                ThreadContextHandler.set(context);
                runnable.run();
            } finally {
              // ③
              ThreadContextHandler.clear();
            }
        };
    }
}
  1. 在线程池创建时将上述类的对象实例化通过taskExecutor.setTaskDecorator添加到线程池创建的配置中。在线程池的初始化时,会判断当前线程池是否存在taskDecorator装饰器,若存在则将该装饰器的处理添加到自身的execute方法中(自身也实现于Runnable接口),也就是当线程调用时会先执行该方法,那么就可以在此进行线程上下文信息的复制。

三、问题

​ 在使用了一段时间后发现了一些问题,由于线程创建时配置的拒绝策略为ThreadPoolExecutor.CallerRunsPolicy(),当请求线程过多导致队列满时,会使用调用的业务线程进行业务逻辑的处理,而这里的finally中在线程结束后会清理上下文信息,这就导致了,当并发达到一定程度时,由于原业务线程被当成线程池线程进行业务处理,而导致执行结束后,上下文信息被清理,使得后面的业务执行中获取不到对应的上下文数据信息。

通过日志发现,原业务线程的线程名称都是由http-nio-xxx,所以简单处理就是对名称进行匹配,若匹配成功则不清理上下文信息。

public class ContextCopyingTaskDecorator implements TaskDecorator {

    static final String HTTP_START_NAME = "http-nio";

    @Override
    public Runnable decorate(Runnable runnable) {
        Map<String, Object> context = ThreadContextHandler.getThreadLocal();
        return () -> {
            // 线程名称不是以http-nio开头的线程, 则为线程池线程
            boolean threadPoolThread = !Thread.currentThread().getName().startsWith(HTTP_START_NAME);
            try {
                if(threadPoolThread){
                    ThreadContextHandler.set(context);
                }
                runnable.run();
            } finally {
                if(threadPoolThread){
                    ThreadContextHandler.clear();
                }
            }
        };
    }
}