• 工作积累——Web请求中使用ThreadLocal遇见的问题


    发现错误

    在我们实现业务的时候会将一些仅用于当前线程的数据使用ThreadLocal 保存起来,在业务执行的某个时机将其取出来,因为线程隔离的特性让我们不必担心其他线程访问到错误的数据。

    最近遇见一个BUG,相同的业务在请求中获取到了不同的返回结果。后来发现是`ThreadLocal``引起的问题。

    ThreadLocal数据被共享?

    ThreadLocal的数据是线程隔离的这个很多人都介绍过了,随着线程销毁,数据也会销毁。那么不同的请求能访问到其他请求中的数据有一个原因可能是ThreadLocal中的数据使用完后未被销毁,而线程被重用了。这个场景可以使用线程池模拟。

    下面代码中模拟向ThreadLocal中设置参数,线程池会重用固定的几个线程,一旦线程重用ThreadLocal 中数据如果没被清空则会出现新的任务读取到旧数据

        private static ThreadLocal<String> threadLocal = new ThreadLocal<>();
    
        public static void main(String[] args) {
    
           ExecutorService executorService = new ThreadPoolExecutor(2,2,0, TimeUnit.SECONDS,new LinkedBlockingDeque<>(12));
    
            List<Runnable> runList = new ArrayList<>();
            for (int i = 0; i < 5; i++) {
                runList.add(() -> {
                    System.out.println("当前线程:" + Thread.currentThread().getName());
                    String value = threadLocal.get();
                    if (value == null) {
                        threadLocal.set(Thread.currentThread().getName());
                        System.out.println("threadLocal 不存在数据,设置参数 :" + Thread.currentThread().getName());
                    } else {
                        System.out.println("threadLocal 已经存在数据,参数 :" + value);
                    }
                });
            }
            runList.forEach(executorService::execute);
            executorService.shutdown();
    
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    在输出的结果中可以看到后面几条任务已经出现错误,读取到了之前线程的数据

    当前线程:pool-1-thread-1
    当前线程:pool-1-thread-2
    threadLocal 不存在数据,设置参数 :pool-1-thread-1
    threadLocal 不存在数据,设置参数 :pool-1-thread-2
    当前线程:pool-1-thread-1
    threadLocal 已经存在数据,参数 :pool-1-thread-1
    当前线程:pool-1-thread-2
    threadLocal 已经存在数据,参数 :pool-1-thread-2
    当前线程:pool-1-thread-1
    threadLocal 已经存在数据,参数 :pool-1-thread-1
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    Tomcat的工作线程

    上面的例子中可以发现,存在线程重用的场景下时ThreadLocal的数据如果没有及时清理会导致重用此线程的业务读取错误的数据。而

    Tomcat在执行请求的工作线程就是从线程池中获取的,在官方文档中关于线程池的配置https://tomcat.apache.org/tomcat-8.5-doc/config/executor.html#Standard_Implementation的文章中指明了默认的线程池org.apache.catalina.core.StandardThreadExecutor

    通过分析初始化的方法和里面的逻辑可以发现,StandardThreadExecutor的实现本质上通过ThreadPoolExecutor实现业务的。

        @Override
        protected void startInternal() throws LifecycleException {
    
            taskqueue = new TaskQueue(maxQueueSize);
            TaskThreadFactory tf = new TaskThreadFactory(namePrefix,daemon,getThreadPriority());
            executor = new ThreadPoolExecutor(getMinSpareThreads(), getMaxThreads(), maxIdleTime, TimeUnit.MILLISECONDS,taskqueue, tf);
            executor.setThreadRenewalDelay(threadRenewalDelay);
            if (prestartminSpareThreads) {
                executor.prestartAllCoreThreads();
            }
            taskqueue.setParent(executor);
    
            setState(LifecycleState.STARTING);
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    需要注意的是:Tomcat的线程池的名字也叫作ThreadPoolExecutor,是继承了JDK的ThreadPoolExecutor然后进行了一些逻辑封装。

    Tomcat的线程队列保存在org.apache.tomcat.util.threads.TaskQueue中就是上面这段代码taskqueue = new TaskQueue(maxQueueSize)

    可以看到Tomcat为了提供处理请求的效率也是使用线程池来处理请求的,这意味的线程会被重用,这样在使用一些线程变量的时候,如果在任务结束后没有主动请求这些数据,这些数据就会污染线程导致后续业务错误。

    ThreadLocal产生的问题却不是ThreadLocal的问题

    ThreadLocal作为一个可以设置线程共用数据的工具,能实现很多业务。很多时候我们在实现某些逻辑的时候没有意识到这些逻辑一直运行在一个多线程环境。用好ThreadLocal,不仅要理解ThreadLocal也要理解运行ThreadLocal的环境。有时ThreadLocal的问题却不是ThreadLocal产生的问题

  • 相关阅读:
    Mybatis使用拦截器添加参数
    多无人机编队集群飞行
    Linux系统上使用Docker部署SpringBoot项目
    电路设计篇【5】MOS管驱动电路设计,如何让MOS管快速开启和关闭?
    力扣(LeetCode)1668. 最大重复子字符串(C++)
    ESP8266-Arduino编程实例-ISL29125 RGB颜色光传感器驱动
    Docker初识
    Spring Security 用了那么久,你对它有整体把控吗?
    C++ primer plus C++的编程模块(1)
    【白嫖8k买的机构vip教程】Appium自动化(3):Appium-Desktop界面介绍
  • 原文地址:https://blog.csdn.net/qq330983778/article/details/125435717