背景:
利用springboot+mysql做一个伪mq,当项目启动后,调用写好的方法每隔30秒获取一次数据,然后利用线程池多线程消费,出异常后递归此方法,形成一个监听服务。
核心代码:
1.项目启动调用查询,此处特备注意下这个接口(InitializingBean)
public class MimidaiOperatorReportApplication implements InitializingBean {
private Logger logger = LoggerFactory.getLogger(MimidaiOperatorReportApplication.class);
@Autowired
private SjmhOpdReportGetController sjmhOpdReportGetController;
public static void main(String[] args) {
SpringApplication.run(MimidaiOperatorReportApplication.class, args);
}
@Override
public void afterPropertiesSet() throws Exception {
String resultNew = sjmhOpdReportGetController.getSjmhReportNew();
logger.info("SjmhGetReportNewTheResultIs:{}",resultNew);
}
}
2.利用线程池消费,每执行一次程序计数器减1
while(true){
for (SjmhReportTask sjmhReportTask : taskList) {
threadPoolForReportTask.execute(new Runnable() {
@Override
public void run() {
try {
//执行业务代码
/*
省略n行代码
**/
insert(student);
} catch (Exception e) {
logger.error("用户userId:{},申请Id:{},upcId:{}请求出错{}", sjmhReportTask.getUserId(),
sjmhReportTask.getApplyId(), sjmhReportTask.getUpcId(), e.getMessage(), e);
} finally {
taskCount.countDown();
}
}
});
}
}
注意:此处insert(student)中student每个字段不能为空。
3.循环监听剩余任务数
while (true) {
if (whileTimes > 6) {
logger.info("=======循环次数已经超过最大值,结束循环======");
setTaskCount(0);
}
Thread.sleep(3000);
logger.info("剩余在跑的任务为:{}条", this.taskCount.getCount());
if (this.taskCount.getCount() < 1) {
//跑完之后释放系统内存
taskList.clear();
break;
}
whileTimes++;
}
问题:
刚开始的时候跑的都很正常,直到有一天,我发现日志中一直打 剩余在跑的任务为:1条,等到循环6次之后跳出循环,这时候查看日志并没有发现什么异常日志,然后开始查看数据,发现有一个表的这条数据没有插入,最后再从日志中抓到这个记录对应的信息,发现student有一个字段为空。导致插入失败。不知 为何异常没有打出来,而且这个线程直接死掉,导致没有执行finally中countdown()。因此日志中一直出现 剩余在跑的任务为:1条的日志。
分析:
1.单元测试。
将这段有问题的代码拿出来执行单元测试,发现异常都能正常输出。
2.一下输出异常信息太多,导致线程卡死
经过多次测试,无此问题。
3.此异常无法捕捉(DataIntegrityViolationException)
手动抛出此异常,而且其他异常均能正常捕获。
网上各种尝试均未能解决,懵逼ing...
契机:
在多次尝试中,发现有一次这个异常在主线程中打了出来,仿佛看到希望。于是突然想起excute抛出异常的时候,有时候会依赖于父线程,我项目启动的时候实现了InitializingBean接口,afterPropertiesSet之中调用我业务方法,此时突然发现当主线程未执行结束的时候,子线程已经开始执行,而就在此时,执行的子线程中有空值,出异常的子线程开始检测是否有父线程,父线程也进入while (true) {}死循环中,因此将异常抛给父线程,当主线程此时已经没有执行权,最终导致异常无法输出。
解决方法:
在整个项目启动后,即主线程执行结束后,开始任务处理,这样父线程就没有了,每个子线程就会调用默认的异常处理hanlder处理异常。
代码如下:
@Component
public class OpdGetReportStarter implements ApplicationRunner {
private Logger logger = LoggerFactory.getLogger(OpdGetReportStarter.class);
@Autowired
private SjmhOpdReportGetController sjmhOpdReportGetController;
@Override
public void run(ApplicationArguments args) throws Exception {
String resultNew = sjmhOpdReportGetController.getSjmhReportNew();
logger.info("获取结果:{}" ,resultNew);
}
}