您的位置:首页 > 编程语言 > Java开发

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及端口号即可连接查看相关信息。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: