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()操作,如果确实需要,必须自测所有情况,以免出现严重的生产事故。