Java-异步多线程专题-AfterCommitExecutor-在线程池中带来ThreadLocal串数据问题
2022-04-02 09:59:31.399 [pool-4-thread-8] DEBUG cn.jiangjiesheng.edu.mapper.train.EduUserOnlineCourseStatusMapper.selectByExample.debug:159 - ==> Parameters: jiangsudenahuaxueanq(String), 0(Integer), 9200236(String), 5085(Integer)
2022-04-02 09:59:31.400 [http-nio-20820-exec-4] DEBUG cn.jiangjiesheng.bootstrap.commons.response.SafetyResponseBodyAdvice.beforeBodyWrite:97 - rest returning data is {"message":null,"result":12.72,"success":true,"trace":null}
2022-04-02 09:59:31.400 [pool-4-thread-8] DEBUG cn.jiangjiesheng.edu.mapper.train.EduUserOnlineCourseStatusMapper.selectByExample.debug:159 - <== Total: 0
2022-04-02 09:59:31.400 [pool-4-thread-8] INFO cn.jiangjiesheng.edu.service.train.EduTrainStatService.lambda$updateOnlineCourseStatus$2:138 - 必须要保证theadLocal正确,后续还有代码需要调用,getCurContainerId:jiangsudenahuaxueanq
.......................
2022-04-02 09:59:32.896 [pool-4-thread-4] DEBUG cn.jiangjiesheng.edu.mapper.course.CourseAssociatedUserMapper.batchInsert.debug:159 - <== Updates: 1
2022-04-02 09:59:32.898 [Pool-4-Thread-8] INFO cn.jiangjiesheng.edu.service.CourseAssociatedUserService.sendDistributeCourseMsgByQiLiao:277 - 课程关联人员分配课程发送启聊消息:course:{"authorityId":-1,"briefIntroduction":"测试培训课程0402-2(岗位培训)","categoryId":1598,"containerId":"AnQuanShengChanGong2","cover":"seats/2022/04/course/image/106702147961cf7624bab12a58f0cb47.jpg","createTime":1648864212000,"createrId":"admin_51","deleteTime":1648864211000,"deleterId":"","detail":"<p>测试培训课程0402-2(岗位培训)</p><p><br></p>","id":5106,"isDelete":0,"latestReleaseTime":1648864762000,"lecturerId":"admin_51","name":"测试培训课程0402-2(岗位培训)","number":"KC1648864211670717","releaseTime":1648864211000,"status":4,"testPaperId":3245,"transcodeStatus":3,"updateTime":1648864762000,"updaterId":""},distributeAction:1,courseAssociatedUserList:[{"containerId":"AnQuanShengChanGong2","courseId":5106,"createTime":1648864772886,"id":634275,"learningRequirement":2,"userId":"admin_51"}]
2022-04-02 09:59:32.899 [pool-4-thread-8] INFO cn.jiangjiesheng.edu.service.CourseAssociatedUserService.doSendDistributeCourseMsgByQiLiao:335 - 课程关联人员分配课程发送启聊消息中止,没有【选修】的课程人员关联列表:course:{"authorityId":-1,"briefIntroduction":"测试培训课程0402-2(岗位培训)","categoryId":1598,"containerId":"AnQuanShengChanGong2","cover":"seats/2022/04/course/image/106702147961cf7624bab12a58f0cb47.jpg","createTime":1648864212000,"createrId":"admin_51","deleteTime":1648864211000,"deleterId":"","detail":"<p>测试培训课程0402-2(岗位培训)</p><p><br></p>","id":5106,"isDelete":0,"latestReleaseTime":1648864762000,"lecturerId":"admin_51","name":"测试培训课程0402-2(岗位培训)","number":"KC1648864211670717","releaseTime":1648864211000,"status":4,"testPaperId":3245,"transcodeStatus":3,"updateTime":1648864762000,"updaterId":""},distributeAction:1
2022-04-02 09:59:32.899 [pool-4-thread-8] INFO cn.jiangjiesheng.edu.service.CourseAssociatedUserService.doSendDistributeCourseMsgByQiLiao:339 - 课程关联人员分配课程发送启聊消息继续发送,有【必修】的课程人员关联列表:course:{"authorityId":-1,"briefIntroduction":"测试培训课程0402-2(岗位培训)","categoryId":1598,"containerId":"AnQuanShengChanGong2","cover":"seats/2022/04/course/image/106702147961cf7624bab12a58f0cb47.jpg","createTime":1648864212000,"createrId":"admin_51","deleteTime":1648864211000,"deleterId":"","detail":"<p>测试培训课程0402-2(岗位培训)</p><p><br></p>","id":5106,"isDelete":0,"latestReleaseTime":1648864762000,"lecturerId":"admin_51","name":"测试培训课程0402-2(岗位培训)","number":"KC1648864211670717","releaseTime":1648864211000,"status":4,"testPaperId":3245,"transcodeStatus":3,"updateTime":1648864762000,"updaterId":""},distributeAction:1
2022-04-02 09:59:32.899 [http-nio-20820-exec-4] DEBUG com.qpaas.service.core.ServiceClient.getResponseContext:180 - Response <<< {"status":200,"code":200,"msg":"","result":{"type":"0","enterprise_id":"jiangsudenahuaxueanq","user_id":"9200171","last_login_time":"2022-03-30 19:54:10","real_name":"陈大勤","theme":null,"avatar":null,"pinyin":"chendaqin","enterprise_name":"江苏德纳化学股份有限公司"},"guid":"99e3c10287369b51"}
2022-04-02 09:59:32.899 [http-nio-20820-exec-4] INFO cn.jiangjiesheng.edu.interceptor.LoginInterceptor.preHandle:48 - 平台session ========= UserSession(containerId=jiangsudenahuaxueanq, containerName=江苏德纳化学股份有限公司, userId=9200171, type=0, realName=陈大勤, pinyin=chendaqin, avatar=null, lastLoginTime=2022-03-30 19:54:10, theme=null)
2022-04-02 09:59:32.899 [http-nio-20820-exec-4] INFO cn.jiangjiesheng.edu.interceptor.LoginInterceptor.preHandle:60 - userId:9200171 ========= containerId:jiangsudenahuaxueanq
2022-04-02 09:59:32.899 [http-nio-20820-exec-4] INFO cn.jiangjiesheng.edu.interceptor.LoginInterceptor.preHandle:62 - url ========= http://10.81.56.245:10090/safetyjapi/paas-server/paas/getUserInfo?userId=9200171&containerId=jiangsudenahuaxueanq
-----看这里
2022-04-02 09:59:32.899 [pool-4-thread-8] DEBUG cn.jiangjiesheng.edu.mapper.message.UserMessageMapper.insertSelective.debug:159 - ==> Preparing: INSERT INTO user_message ( enterprise_id,message_type,user_id,content,redirect_url,param,create_time,update_time ) VALUES( ?,?,?,?,?,?,?,? )
2022-04-02 09:59:32.899 [pool-4-thread-8] DEBUG cn.jiangjiesheng.edu.mapper.message.UserMessageMapper.insertSelective.debug:159 - ==> Parameters: jiangsudenahuaxueanq(String), 23(Byte), admin_51(String), 新课程【测试培训课程0402-2(岗位培训)】已经添加到您的必修课程中,点击开始学习(String), /safetyapp/educate/view/courseDetail?id=5106(String), {"id":5106,"suffix":"2022-04","containerId":"AnQuanShengChanGong2","courseId":5106,"userId":"admin_51","distributeAction":1}(String), 1648864772(Integer), 1648864772(Integer)
2022-04-02 09:59:32.899 [pool-4-thread-8] DEBUG cn.jiangjiesheng.edu.mapper.message.UserMessageMapper.insertSelective.debug:159 - ==> Parameters: jiangsudenahuaxueanq(String), 23(Byte), admin_51(String), 新课程【测试培训课程0402-2(岗位培训)】已经添加到您的必修课程中,点击开始学习(String), /safetyapp/educate/view/courseDetail?id=5106(String), {"id":5106,"suffix":"2022-04","containerId":"AnQuanShengChanGong2","courseId":5106,"userId":"admin_51","distributeAction":1}(String), 1648864772(Integer), 1648864772(Integer)
--------------------------------------------------------------------------------
@Autowired
private AfterCommitExecutor afterCommitExecutor;
private ExecutorService threadPool1 = Executors.newFixedThreadPool(5);
@GetMapping("/testAfterCommitExecutor")
public void testAfterCommitExecutor(Integer trainForm, String containerId, Integer implementId, Integer examId) throws InterruptedException {
for (int i = 0;i<100;i++){
new Thread(() -> {}).start();
int finalI = i;
//模拟实际场景,最后任务执行的线程名是pool-7-thread-5这种,
// 如果new Thread(() -> {}).start(),这是多线程,最后任务执行的线程名thread-4
threadPool1.submit(new Runnable() {
@Override
public void run() {
if(finalI % 3 ==0){//模拟有的set,有的没set
EduTrainBaseService.setAsyncThreadUserInfo("容器"+ finalI, null);
EduTrainImplement eduTrainImplement = new EduTrainImplement();
eduTrainImplement.setContainerId("容器"+ finalI);
AyStatic.setThreadLocalTrainImplement(eduTrainImplement);
}
mockDbUpdate(finalI);
}
});
}
}
@Transactional(timeout = 2)
public void mockDbUpdate(int taskId){
try {
Thread.sleep(200);
EduImplementAssociatedExam eduImplementAssociatedExam = new EduImplementAssociatedExam();
eduImplementAssociatedExam.setId(1);
eduImplementAssociatedExam.setUpdateTime(new Date());
eduImplementAssociatedExamMapper.updateByPrimaryKeySelective(eduImplementAssociatedExam);
} catch (InterruptedException e) {
}
afterCommitExecutor.execute(new Runnable() {
@Override
public void run() {
doAfterCommitExecutorTask(taskId);
}
});
}
private void doAfterCommitExecutorTask(int taskId){
log.info("taskId="+taskId+ "UserInfoUtil.getCurContainerId()="+(UserInfoUtil.getUserInfo() !=null ? UserInfoUtil.getCurContainerId():""));
log.info("taskId="+taskId+ "UserInfoUtil.getThreadLocalTrainImplement()="+(AyStatic.getThreadLocalTrainImplement() !=null ? AyStatic.getThreadLocalTrainImplement().getContainerId():""));
if(!((UserInfoUtil.getUserInfo() !=null ? UserInfoUtil.getCurContainerId():"")).equals("容器"+taskId)){
//不等于的情况
log.error("taskId="+taskId+ "UserInfoUtil.getThreadLocalTrainImplement()="+(AyStatic.getThreadLocalTrainImplement() !=null ? AyStatic.getThreadLocalTrainImplement().getContainerId():""));
}
if(!((AyStatic.getThreadLocalTrainImplement() !=null ? AyStatic.getThreadLocalTrainImplement().getContainerId():"").equals("容器"+taskId))){
//不等于的情况
log.error("taskId="+taskId+ "UserInfoUtil.getThreadLocalTrainImplement()="+(AyStatic.getThreadLocalTrainImplement() !=null ? AyStatic.getThreadLocalTrainImplement().getContainerId():""));
}
// 2022-04-02 16:00:32.653 [pool-7-thread-2] INFO cn.jiangjiesheng.edu.controller.frontend.test.TestController.doAfterCommitExecutorTask:469 -taskId=70UserInfoUtil.getCurContainerId()=容器36
// 2022-04-02 16:00:32.653 [pool-7-thread-5] INFO cn.jiangjiesheng.edu.controller.frontend.test.TestController.doAfterCommitExecutorTask:469 -taskId=71UserInfoUtil.getCurContainerId()=容器66
// 2022-04-02 16:00:32.653 [pool-7-thread-1] INFO cn.jiangjiesheng.edu.controller.frontend.test.TestController.doAfterCommitExecutorTask:469 -taskId=72UserInfoUtil.getCurContainerId()=容器72
// 2022-04-02 16:00:33.491 [pool-7-thread-1] INFO cn.jiangjiesheng.edu.controller.frontend.test.TestController.doAfterCommitExecutorTask:469 -taskId=92UserInfoUtil.getCurContainerId()=容器72
// 2022-04-02 16:15:46.646 [pool-7-thread-4] INFO cn.jiangjiesheng.edu.controller.frontend.test.TestController.doAfterCommitExecutorTask:480 -taskId=94UserInfoUtil.getThreadLocalTrainImplement()=容器75
// 2022-04-02 16:15:46.646 [pool-7-thread-2] INFO cn.jiangjiesheng.edu.controller.frontend.test.TestController.doAfterCommitExecutorTask:480 -taskId=91UserInfoUtil.getThreadLocalTrainImplement()=容器54
// 2022-04-02 16:15:46.646 [pool-7-thread-1] INFO cn.jiangjiesheng.edu.controller.frontend.test.TestController.doAfterCommitExecutorTask:480 -taskId=92UserInfoUtil.getThreadLocalTrainImplement()=容器60
//上述 taskId 期望和 getCurContainerId() 相等(如果有值的话)
//但是综合来看,thread-1 的getCurContainerId() 相等。
//pool-7-thread-1:7就表示第7个线程池,1表示所在线程池7内是第1个线程
//结合new Thread(() -> {}).start()的用法
//得出结论,ThreadLocal(TransmittableThreadLocal同样也是)的值只绑定在thread线程上,由于线程池中的线程资源是重复使用的,等于同一个线程池中的同一个thread线程一直没有变
// ,如果真要去修改,一定是要找到第几个线程池的第几个线程。
//解决方案:
//直接原因还是执行afterCommitExecutor前没有重新设置 ThreadLocal(也注意有的封装代码会判断为空时才去修改,所以要先clear),导致被后面的任务共用了
//afterCommitExecutor执行完后清空ThreadLocal数据,对于同一个线程来说,这个值也就没了,
//对于同一个线程来说,同一时间只会执行一个任务,下一个任务可能就没ThreadLocal数据值了,也就是要保证调用afterCommitExecutor前一定要重新set ThreadLocal
//验证后结论,
//1、在执行afterCommitExecutor前重设 ThreadLocal,
//2、这种实测不行,貌似会影响到其他地方的调用,AfterCommitExecutorImpl执行完Runnable任务后clear ThreadLocal,就可保证数据正确(要么有值,要么没重设ThreadLocal而查询为空)
}