SpringTask执行定时任务中调用方法中断问题
2017-11-26 16:51
369 查看
背景
使用SpringQuartz轻量级定时任务时,出现任务中的方法调用链未执行完,也未抛出异常,然后到下一次时间就继续执行下一次的任务。刚开始时百度一下,以为是线程阻塞、并发设置等(默认是并发执行)。然后顺着这个思路一直往下搜索资料,找到的是线程阻塞,然后不理解为什么阻塞,用了各种方法,包括Java VisualVM监控器来监听Tomcat的线程问题,查看哪些线程waitable;事后证明是我多想了,并没有等待线程,也没有CPU非常高的现象。耐心再debug几次发现有几个异常,可是一直都没有抛出来,直到追踪到一个定时任务线程中的异常信息才发现,是Spring定时任务框架将异常捕获了,导致控制台没有输出。细想定时任务这么设计的原因,否则可能会因为异常原因而导致大量阻塞无法进行下一次定时任务。过程
原因被以下任务调度线程捕获而未打印到控制台。这点可以通过eclipse中的Debug调试在线程栈中找到,运行时主要调用类如下:
SpringTask是如何通过注解来@Scheduled来运行定时任务的?
首先要明白的一点是定时任务都是基于多线程来执行的,如Timer或TimerTask等都是基于多线程的,而在java并发包中有个ScheduledThreadPool是专门用来解决定时任务线程的问题。
SpringTask执行定时任务的方法是
org.springframework.scheduling.support.ScheduledMethodRunnable.ScheduledMethodRunnable类中的run()方法,该类实现了Runnable方法;构造方法与源代码如下:
private final Object target; private final Method method; public ScheduledMethodRunnable(Object target, Method method) { this.target = target; this.method = method; } @Override public void run() { try { ReflectionUtils.makeAccessible(this.method); this.method.invoke(this.target); } catch (InvocationTargetException ex) { ReflectionUtils.rethrowRuntimeException(ex.getTargetException()); } catch (IllegalAccessException ex) { throw new UndeclaredThrowableException(ex); } }
因此
ScheduledMethodRunnable类的主要作用就是创建一个线程代理执行定时任务方法。并且在执行方法过程中自定义的方法(定时任务)如果发生异常,尤其是运行时异常则会层层抛出,直到这个run()方法捕获,因此才会出现本次案例中的错解,误以为定时任务线程阻塞或其它原因。而在本例中的任务执行中会调用mybatis查询数据库,如果出现数据库异常的话,则无法通过run方法抛出RuntimeException,原因在于SqlException不属于RuntimeException。
继续往下看,查看构造方法的调用链。
在doWith方法中发现熟悉的postProcessAfterInitialization()实现,这个是Spring生命周期中容器级别的注入方法,接口是BeanPostProcessor,用于在容器初始化所有的bean前后做一些业务处理。postProcessAfterInitialization()业务中具体对所有的bean中的方法搜索是否有@Scheduled注解,然后通过反射得到类和方法的信息等。至此我们明白了SpringTask通过@Scheduled获取执行任务的过程。
@Override public Object postProcessAfterInitialization(final Object bean, String beanName) { Class<?> targetClass = AopUtils.getTargetClass(bean); if (!this.nonAnnotatedClasses.contains(targetClass)) { final Set<Method> annotatedMethods = new LinkedHashSet<Method>(1); ReflectionUtils.doWithMethods(targetClass, new MethodCallback() { @Override public void doWith(Method method) throws IllegalArgumentException, IllegalAccessException { for (Scheduled scheduled : AnnotationUtils.getRepeatableAnnotation(method, Schedules.class, Scheduled.class)) { processScheduled(scheduled, method, bean); annotatedMethods.add(method); } } }); if (annotatedMethods.isEmpty()) { this.nonAnnotatedClasses.add(targetClass); if (logger.isDebugEnabled()) { logger.debug("No @Scheduled annotations found on bean class: " + bean.getClass()); } } else { // Non-empty set of methods if (logger.isDebugEnabled()) { logger.debug(annotatedMethods.size() + " @Scheduled methods processed on bean '" + beanName + "': " + annotatedMethods); } } } return bean; }
解决
定时任务方法要么抛异常,要么对整个方法内的业务捕获异常并处理。本次解决采用的是捕获异常并打印消息方便维护。
@Scheduled("0 0/5 * * * *") void excuteTask() { try { system.err.println("测试。。。"); //TODO } cathch (Exception e) { logger.error("erroro is {}", e); } }
总结
对于eclipse debug模式并不熟练,对于线程栈也没有理清楚。出现问题,先从debug开始耐心一步一步找到问题然后解决。
其它
如何通过VisualVM监听Tomcat运行状态?
VisualVM要监听Tomcat需要Tomcat配置可以通过JMX端口被监听才可以。windows具体方法如下,在catalina.bat文件中(Linux中是catalina.sh文件,具体网上搜索)的
rem Guess CATALINA_HOME if not defined位置下添加
set JAVA_OPTS=-Dcom.sun.management.jmxremote.port=9090 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false一行语句,其中9090是监听端口,然后打开VisualVM开始JMX连接,输入IP及端口号即可连接查看相关信息。
相关文章推荐
- 解决Spring定时计划任务重复执行两次(实例被构造两次)问题的方法
- 解决Spring定时计划任务重复执行两次或多次(实例被构造两次)问题的方法
- 解决Spring定时计划任务重复执行两次(实例被构造两次)问题的方法
- spring定时任务执行两次及tomcat部署缓慢问题的解决方法
- 解决Spring定时计划任务重复执行两次(实例被构造两次)问题的方法
- 解决Spring定时计划任务重复执行两次(实例被构造两次)问题的方法
- 解决Spring定时计划任务重复执行两次(实例被构造两次)问题的方法
- 解决Spring定时计划任务重复执行两次(实例被构造两次)问题的方法
- 解决Spring定时计划任务重复执行两次(实例被构造两次)问题的方法
- Spring 被初始化两次(Spring-Task定时任务执行两次)分析和解决方法
- 解决Spring定时计划任务重复执行两次(实例被构造两次)问题的方法
- 解决Spring自带的定时任务执行两次的问题
- 关于spring的quartz(定时任务执行)问题,
- 项目启动后开启定时任务方法-->TimerTask中如何调用service
- SSH:解决Spring整合quartz定时任务执行两次问题
- spring定时任务执行两次的原因与解决方法
- spring中集成TimerTask执行定时任务
- spring quartz 定时任务执行两次的问题
- Spring Boot使用方法小札(2):执行定时任务
- spring中集成TimerTask执行定时任务