场景:消息通知会发送给自己,微信公众号上的通知,提醒人与操作人不符合
思路:@Async 异步执行的时候,因为不在与主线程不是同一个线程所以,UserUtil.getCurrentUser()丢失当前用户信息,重新设置了用户上下文信息,以此为出发点排查。
1、首先在调用@Async方法的地方打上断点
2、在@Async执行的方法打上断点
3、设置 @Async 的核心线程池数量为1,这样保证多个请求执行的时候,@Async的线程都一样的
threadPoolTaskExecutor.setCorePoolSize(1);
@Configuration
public class TaskAsyncPoolConfig {
/**
* 异步线程池
*/
@Bean
public ThreadPoolTaskExecutor taskAsyncExecutor() {
ThreadPoolTaskExecutor threadPoolTaskExecutor = new ThreadPoolTaskExecutor();
threadPoolTaskExecutor.setCorePoolSize(1);
threadPoolTaskExecutor.setMaxPoolSize(100);
threadPoolTaskExecutor.setQueueCapacity(20);
threadPoolTaskExecutor.setThreadNamePrefix("async-task-");
threadPoolTaskExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
threadPoolTaskExecutor.initialize();
return threadPoolTaskExecutor;
}
}
4、debug模式下,添加参数查看
也可通过 Evaluate Expression 查看参数值
5、本地调试发起两次请求,用户ID不同
第一个请求:
用户id:9012827864423877687
@Async调用前线程:http-nio-8098-exec-4
@Async执行线程:async-task-1
UserUtil.getCurrentUser() == null
第二个请求:
用户id:1252476470807281738
@Async调用前线程:http-nio-8098-exec-2
@Async执行线程:async-task-1
UserUtil.getCurrentUser() 对应的用户id为 9012827864423877687
疑问?第一次请求UserUtil.getCurrentUser() == null,第二次请求UserUtil.getCurrentUser() != null
所以后面的每次请求因为@Async的核心线程池数设置为1,所以正常请求下,UserUtil.getCurrentUser() 都为第一次请求时候设置的那个用户信息
再来看下UserUtil.getCurrentUser() 是如何取值的
/**
* 用户信息内容上下文
* @author chongyang
*
*/
public class UserContext {
private static org.slf4j.Logger logger = LoggerFactory.getLogger(UserContext.class);
/**用户信息*/
private final static ThreadLocal<User> USER_CONTEXT = new ThreadLocal<User>();
public static void setUser(User user) {
USER_CONTEXT.remove();
if (user != null) {
logger.debug("设置当前用户信息:用户ID:{}", user.getId());
USER_CONTEXT.set(user);
}
}
/**
* 获取用户信息
* @return
*/
public static User get() {
return USER_CONTEXT.get();
}
/**
* 清除用户信息
*/
public static void remove() {
USER_CONTEXT.remove();
}
}
ThreadLocal 看来问题找到了
private final static ThreadLocal<User> USER_CONTEXT = new ThreadLocal<User>();
简单来说可以在指定线程内存储数据,数据存储以后,只有指定线程可以得到存储数据。
如果不熟悉ThreadLocal ,可以看下面这篇文章
https://www.jianshu.com/p/3c5d7f09dfbd
如何解决?
@Async 执行完成后,在finally调用UserUtil.clearCurrentUser(); 清除ThreadLocal中设置的User
延申场景:@Async配置10个核心线程池,这样每次请求到不同的线程,
async-task-1 ~ async-task-10,每次线程第一个访问的用户ID不同,ThreadLocal的User即为第一次请求的用户,如果没有UserUtil.clearCurrentUser()清除当前ThreadLocal的User,会造成多次发起文件状态修改请求,微信消息通知的提醒人不一致的情况。也可能当前用户与线程中的设置的用户一致的情况,这种情况消息通知不会发送给自己,是正常的。
threadPoolTaskExecutor.setCorePoolSize(10);
@Configuration
public class TaskAsyncPoolConfig {
/**
* 异步线程池
*/
@Bean
public ThreadPoolTaskExecutor taskAsyncExecutor() {
ThreadPoolTaskExecutor threadPoolTaskExecutor = new ThreadPoolTaskExecutor();
threadPoolTaskExecutor.setCorePoolSize(10);
threadPoolTaskExecutor.setMaxPoolSize(100);
threadPoolTaskExecutor.setQueueCapacity(20);
threadPoolTaskExecutor.setThreadNamePrefix("async-task-");
threadPoolTaskExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
threadPoolTaskExecutor.initialize();
return threadPoolTaskExecutor;
}
}