对于ThreadLocal对象使用的一些思考

1.ThreadLocal对象是存储线程本地变量的。
在项目中经常存储一些全局切面使用的变量,比如一些动态数据源的dataSource信息。

    @Pointcut(value = "execution(public * xxx.xxx.dao..*.*(..))")
    public void pointcut() {
        log.info("DynamicChangeDataSourceComponent.pointcut..");
    } 

@Around("pointcut()")
    public Object doChange(ProceedingJoinPoint pjp) {
        String classPath = null;
        String methodName = null;
        try {
            // 开始时间
            long beginTime = System.nanoTime();
            // mapper 路径
            classPath = pjp.getSignature().getDeclaringTypeName();
            // 调用的方法名
            methodName = pjp.getSignature().getName();
            if (Boolean.TRUE.equals(changeDataSouceEnable)) {
                DynamicDataSourceContext.setDataSource(this.getCurrentRequest());
            } else {
                DynamicDataSourceContext.setDataSource(DynamicDataSource.DEFAULT_DATASOURCE);
            }
            Object obj = pjp.proceed();
            // 结束时间
            long endTime = System.nanoTime();
            // 执行耗时(单位:毫秒)
            long executeTime = BaseUtils.calculateNanoTimeDifferenceToMillisecond(beginTime, endTime);
            if (!BaseUtils.checkRequired(classPath, methodName)) {
                return obj;
            }
            // 打印service调用日志
            this.printServiceLog(pjp, obj, classPath, methodName, executeTime);
            return obj;
        } catch (Throwable e) {
            throw new AuroraRunTimeException(classPath + Constants.WELL_NUMBER_SYMBOL + methodName + " 执行异常", e);
        } finally {
            if (Boolean.TRUE.equals(changeDataSouceEnable)) {
                DynamicDataSourceContext.clearDataSource();
            }
        }
    }

    private String getCurrentRequest() {
        Request request = DataSourceThreadLocal.get();
        if (Objects.isNull(request) || Objects.isNull(request.getTenantId())) {
            log.error("PRO DataSourceThreadLocal.getCurrentRequest 当前线程没有Request信息");
            throw new AuroraRunTimeException("当前线程没有DataSource信息");
        }
        return BaseUtils.formatStr(Constants.DATASOURCE_FORMAT, request.getTenantId().toString());
    }

上面代码这样使用是没有问题的,但是他会伴随的衍生出其他问题。
例如在使用线程池操作数据库的时候,会有意向不到的问题发生
示例代码:

 // 执行具体跑批任务
    @Bean(INTENT_TASK_EXECUTORS)
    public Executor intentTaskExecutors() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(Runtime.getRuntime().availableProcessors());
        executor.setMaxPoolSize(Runtime.getRuntime().availableProcessors());
        executor.setQueueCapacity(this.queueSize);
        executor.setKeepAliveSeconds(KEEP_ALIVE_SECOND);
        executor.setThreadNamePrefix(INTENT_TASK_EXECUTORS + "-");
        // 当前加入到线程池任务的线程执行任务
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        return executor;
    }

 executor.execute(()->{
                            try{
                                DataSourceThreadLocal.insert(request);
                                runDetail(intentTaskConfig, countDownLatch, detail);
                            }finally {
                                DataSourceThreadLocal.remove();
                            }
                        });

由于使用CallerRunsPolicy的拒绝策略,在触发拒绝策略的时候,会执行DataSourceThreadLocal.remove(),这会将当前主工作线程的DataSoiurceThreadLocal中的数据源变量清空。之后再执行循环操作的时候,查询数据库时,会找不到当前线程的数据源变量。

解决方法

对线程池添加自定义的拒绝策略

// 执行具体跑批任务
    @Bean(INTENT_TASK_EXECUTORS)
    public Executor intentTaskExecutors() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(Runtime.getRuntime().availableProcessors());
        executor.setMaxPoolSize(Runtime.getRuntime().availableProcessors());
        executor.setQueueCapacity(this.queueSize);
        executor.setKeepAliveSeconds(KEEP_ALIVE_SECOND);
        executor.setThreadNamePrefix(INTENT_TASK_EXECUTORS + "-");
        // 当前加入到线程池任务的线程执行任务
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy(){
            @Override
            public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
                Request request = CurrentTenantThreadLocalComponent.get();
                try{
                    super.rejectedExecution(r, e);
                }finally {
                    CurrentTenantThreadLocalComponent.insert(request);
                }
            }
        });
        return executor;
    }

思考

在对 ThreadLocal进行全局变量操作的时候,不要轻易的自己添加 remove()操作,如果确实需要,必须自测所有情况,以免出现严重的生产事故。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容