Java线程池——ThreadLocal上下文污染问题
文章目录前言什么是ThreadLocal上下文污染自定义线程池验证定义 ThreadLocal 操作工具类创建自定义线程池案例验证处理方式自定义环绕处理类线程池配置类中绑定线程执行器总结前言池化技术在Spring应用中比较常见通常有一些数据库连接池、自定义连接池、Tomcat 请求线程池等等。池化技术极大的增加了线程的复用性降低了线程的频繁创建和销毁的性能开销问题。但不规范的线程池使用会导致一些极大的生产问题。下面以自定义线程池为例重点说明一下ThreadLocal上下文污染问题以及处理方式。什么是ThreadLocal上下文污染说上下文污染问题首先先说一下线程池的原理、使用和线程销毁等细节。在Java语言中线程池的创建申明通常使用JUC中给定的API实现。可以看看往期我的博客JUC之线程池JUC之线程池七大参数理解线程池的核心通常分为核心线程、队列、非核心线程(最大减去核心数)、拒绝策略等。线程池在使用时通常分为以下几个阶段1、首次调用使用时线程池分配核心线程进行处理。2、第二次调用使用时线程池判断核心线程是否全部占用若未全部占用则继续分配核心线程处理若已全部占用则将该线程处理置于队列中。3、后续调用使用时监测到核心线程满队列满时则会构建空闲线程用于逻辑处理。4、超出最大线程数和队列长度时后续调用使用会直接进行拒绝策略。这里面有几个要点1、线程池初始化时就会自动创建指定核心数的半激活线程置于池中。2、非核心线程在使用后不会立即进行清理会根据线程池的算法机制判断空闲线程的销毁时机。3、Java应用不销毁或者容器不销毁创建的自定义线程池不会进行销毁操作。这样就会引发一个问题如果在某个核心线程使用时给这个线程的ThreadLocalMap绑定某些值在使用完成后也不去clear。在后续使用线程池时就可能会拿到复用的线程在不设置值的情况下就能取到之前业务操作设定的属性值。导致上下文污染也称为 内存泄漏。自定义线程池验证定义 ThreadLocal 操作工具类定义一个ThradLocal的操作类提供设置值和获取值的方法importcn.hutool.core.map.MapUtil;importjava.util.Map;publicclassSecurityContextHolder{// 这是唯一能作为 ThreadLocalMap key 的东西privatestaticfinalThreadLocalMapString,ObjectCONTEXTnewThreadLocal();publicstaticvoidset(Stringkey,Objectvalue){MapString,ObjectmapCONTEXT.get();if(mapnull){mapMapUtil.newHashMap();CONTEXT.set(map);}map.put(key,value);}publicstaticvoidsetAll(MapString,ObjectmapData){if(mapDatanull||mapData.isEmpty())return;MapString,ObjectmapCONTEXT.get();if(mapnull){mapMapUtil.newHashMap();CONTEXT.set(map);}map.putAll(mapData);}/** * Get all key-value pairs stored in the current threads ThreadLocal context. * * return a Map containing all context data, or null if no context is set */publicstaticMapString,ObjectgetAll(){returnCONTEXT.get();}publicstaticObjectget(Stringkey){MapString,ObjectmapCONTEXT.get();returnmapnull?null:map.get(key);}publicstaticvoidclear(){CONTEXT.remove();}}创建自定义线程池定义线程池设定关键参数importcom.google.common.util.concurrent.ThreadFactoryBuilder;importorg.springframework.context.annotation.Bean;importorg.springframework.context.annotation.Configuration;importorg.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;importorg.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;importjava.util.concurrent.ExecutorService;importjava.util.concurrent.LinkedBlockingQueue;importjava.util.concurrent.ThreadFactory;importjava.util.concurrent.ThreadPoolExecutor;importjava.util.concurrent.TimeUnit;ConfigurationpublicclassAsyncTaskTheadPoolConfig{/** * 核心线程数 * 默认的核心线程数为1 */privatestaticfinalintCORE_POOL_SIZE5;/** * 最大线程数 * 默认的最大线程数是Integer.MAX_VALUE 即2sup31/sup-1 */privatestaticfinalintMAX_POOL_SIZE1500;/** * 缓冲队列数 * 默认的缓冲队列数是Integer.MAX_VALUE 即2sup31/sup-1 */privatestaticfinalintQUEUE_CAPACITY4000;/** * 允许线程空闲时间 * 默认的线程空闲时间为60秒 */privatestaticfinalintKEEP_ALIVE_SECONDS60;/** * 线程池前缀名 */privatestaticfinalStringTHREAD_NAME_PREFIXTask_Service_Async_;publicstaticThreadPoolTaskExecutorexecutor;publicstaticThreadPoolTaskSchedulertaskScheduler;/** * allowCoreThreadTimeOut为true则线程池数量最后销毁到0个 * allowCoreThreadTimeOut为false * 销毁机制超过核心线程数时而且超过最大值或者timeout过就会销毁。 * 默认是false */privatebooleanallowCoreThreadTimeOutfalse;BeanpublicThreadPoolTaskSchedulerthreadPoolTaskScheduler(ThreadPoolTaskExecutortaskExecutor){ThreadPoolTaskSchedulerthreadPoolTaskSchedulernewThreadPoolTaskScheduler();taskSchedulerthreadPoolTaskScheduler;threadPoolTaskScheduler.setThreadFactory(taskExecutor);// 指定线程处理 环绕 方法spring 3.0 提供//threadPoolTaskScheduler.setTaskDecorator(new MyTaskDecorator());returnthreadPoolTaskScheduler;}BeanpublicExecutorServicegetExecutorService(){ThreadFactorynamedThreadFactorynewThreadFactoryBuilder().setNameFormat(THREAD_NAME_PREFIX%d).build();returnnewThreadPoolExecutor(CORE_POOL_SIZE,MAX_POOL_SIZE,KEEP_ALIVE_SECONDS,TimeUnit.MILLISECONDS,newLinkedBlockingQueue(QUEUE_CAPACITY),namedThreadFactory,newThreadPoolExecutor.AbortPolicy());}BeanpublicThreadPoolTaskExecutortaskExecutor(){ThreadPoolTaskExecutortaskExecutornewThreadPoolTaskExecutor();taskExecutor.setCorePoolSize(CORE_POOL_SIZE);taskExecutor.setMaxPoolSize(MAX_POOL_SIZE);taskExecutor.setQueueCapacity(QUEUE_CAPACITY);taskExecutor.setKeepAliveSeconds(KEEP_ALIVE_SECONDS);taskExecutor.setThreadNamePrefix(THREAD_NAME_PREFIX);taskExecutor.setAllowCoreThreadTimeOut(allowCoreThreadTimeOut);taskExecutor.setRejectedExecutionHandler(newThreadPoolExecutor.CallerRunsPolicy());//taskExecutor.setTaskDecorator(new MyTaskDecorator());//线程池初始化taskExecutor.initialize();executortaskExecutor;returntaskExecutor;}}案例验证如下所示以下逻辑进行验证importcom.xj.AiMcpToolApplication;importcom.xj.config.AsyncTaskTheadPoolConfig;importcom.xj.constant.SecurityContextHolder;importorg.junit.jupiter.api.Test;importorg.springframework.beans.factory.annotation.Autowired;importorg.springframework.boot.test.context.SpringBootTest;importjava.util.concurrent.TimeUnit;SpringBootTest(classesAiMcpToolApplication.class)publicclassSpringBootTest2{AutowiredprivateAsyncTaskTheadPoolConfigasyncTaskTheadPoolConfig;Testpublicvoidtest1()throwsInterruptedException{System.out.println(主线程打印);asyncTaskTheadPoolConfig.executor.submit(()-{System.out.println(Thread.currentThread().getName() 子线程 set ThreadLocalMap 值);// 设置 ThradLocalMap 值SecurityContextHolder.set(name,xj);});// 循环操作 次数最好高于设定核心线程数for(inti0;i10;i){TimeUnit.SECONDS.sleep(2);asyncTaskTheadPoolConfig.executor.submit(()-{System.out.println(Thread.currentThread().getName() 子线程 get ThreadLocalMap 值);System.out.println(SecurityContextHolder.get(name));});}}}运行后控制台的效果如下处理方式通常来说从线程池中获取某个线程使用后及时地将ThreadLocalMap进行清理即可。低版本中可以这样使用asyncTaskTheadPoolConfig.executor.submit(()-{try{SecurityContextHolder.set(name,xj);doSomething();}finally{SecurityContextHolder.clear();}});在Spring 4.3中对应线程池调度器ThreadPoolTaskExecutor有一个特殊的处理方式org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor#setTaskDecoratorTaskDecorator taskDecorator。看源码可以得知TaskDecorator是一个接口也就是说可以自定义某个类实现这个接口重写其中的核心逻辑就能使用。自定义环绕处理类importcom.crfsdi.amt.constant.SecurityContextHolder;importorg.springframework.core.task.TaskDecorator;importjava.util.Map;publicclassMyTaskDecoratorimplementsTaskDecorator{OverridepublicRunnabledecorate(Runnablerunnable){MapString,ObjectallParentMapSecurityContextHolder.getAll();return()-{try{// 子线程继承父线程的SecurityContextSecurityContextHolder.setAll(allParentMap);runnable.run();// 执行异步任务}finally{// 线程池任务执行结束则清理上下文// 子线程执行结束后清理SecurityContextHolderSecurityContextHolder.clear();System.out.println(finally 清理 ThreadLocalMap 数据);}};}}线程池配置类中绑定线程执行器BeanpublicThreadPoolTaskSchedulerthreadPoolTaskScheduler(ThreadPoolTaskExecutortaskExecutor){ThreadPoolTaskSchedulerthreadPoolTaskSchedulernewThreadPoolTaskScheduler();taskSchedulerthreadPoolTaskScheduler;threadPoolTaskScheduler.setThreadFactory(taskExecutor);// spring 6.2 版本才有 绑定自定义处理类threadPoolTaskScheduler.setTaskDecorator(newMyTaskDecorator());returnthreadPoolTaskScheduler;}BeanpublicThreadPoolTaskExecutortaskExecutor(){ThreadPoolTaskExecutortaskExecutornewThreadPoolTaskExecutor();taskExecutor.setCorePoolSize(CORE_POOL_SIZE);taskExecutor.setMaxPoolSize(MAX_POOL_SIZE);taskExecutor.setQueueCapacity(QUEUE_CAPACITY);taskExecutor.setKeepAliveSeconds(KEEP_ALIVE_SECONDS);taskExecutor.setThreadNamePrefix(THREAD_NAME_PREFIX);taskExecutor.setAllowCoreThreadTimeOut(allowCoreThreadTimeOut);taskExecutor.setRejectedExecutionHandler(newThreadPoolExecutor.CallerRunsPolicy());// 绑定自定义执行器taskExecutor.setTaskDecorator(newMyTaskDecorator());//线程池初始化taskExecutor.initialize();executortaskExecutor;returntaskExecutor;}再次调用上面的测试类。控制台打印信息如下总结使用完线程池中的线程后需要及时清理线程的ThreadLocalMap值