技术开发 频道

如何处理任务调度程序的若干问题?


Web应用程序中调度器的启动和关闭问题 

    我们知道静态变量是ClassLoader级别的,如果Web应用程序停止,这些静态变量也会从JVM中清除。但是线程则是JVM级别的,如果你在Web应用中启动一个线程,这个线程的生命周期并不会和Web应用程序保持同步。也就是说,即使你停止了Web应用,这个线程依旧是活跃的。正是因为这个很隐晦的问题,所以很多有经验的开发者不太赞成在Web应用中私自启动线程。 

    如果我们手工使用JDK Timer(Quartz的Scheduler),在Web容器启动时启动Timer,当Web容器关闭时,除非你手工关闭这个Timer,否则Timer中的任务还会继续运行! 

    下面通过一个小例子来演示这个“诡异”的现象,我们通过ServletContextListener在Web容器启动时创建一个Timer并周期性地运行一个任务:
代码清单StartCycleRunTask:容器监听器 package com.baobaotao.web; import java.util.Date; import java.util.Timer; import java.util.TimerTask; import javax.servlet.ServletContextEvent; import javax.servlet.ServletContextListener; public class StartCycleRunTask implements ServletContextListener { private Timer timer; public void contextDestroyed(ServletContextEvent arg0) {
②该方法在Web容器关闭时执行 System.out.println(
"Web应用程序启动关闭..."); } public void contextInitialized(ServletContextEvent arg0) {
②在Web容器启动时自动执行该方法 System.out.println(
"Web应用程序启动..."); timer = new Timer();②-1:创建一个Timer,Timer内部自动创建一个背景线程 TimerTask task = new SimpleTimerTask(); timer.schedule(task, 1000L, 5000L); ②-2:注册一个5秒钟运行一次的任务 } } class SimpleTimerTask extends TimerTask {③任务 private int count; public void run() { System.out.println((++count)+"execute task..."+(new Date())); } } 在web.xml中声明这个Web容器监听器: <?xml version="1.0" encoding="UTF-8"?> <web-app> <listener> <listener-class>com.baobaotao.web.StartCycleRunTask</listener-class> </listener> </web-app>
    在Tomcat中部署这个Web应用并启动后,你将看到任务每隔5秒钟执行一次,在控制台上打出以下的信息:


Web应用程序启动,任务周期运行

    运行一段时间后,登录Tomcat管理后台,将对应的Web应用(chapter13)关闭,Tomcat的后台管理界面下图所示:


在Tomcat管理后台关闭相应的Web应用

    转到Tomcat控制台,你将看到虽然Web应用已经关闭,但Timer任务还在我行我素地执行如故——舞台已经拆除,戏子继续表演:


Web应用关闭后,任务依旧继续执行

    我们可以通过改变清单StartCycleRunTask的代码,在contextDestroyed(ServletContextEvent arg0)中添加timer.cancel()代码,在Web容器关闭后手工停止Timer来结束任务。 

    Spring为JDK Timer和Quartz Scheduler所提供的TimerFactoryBean和SchedulerFactoryBean能够和Spring容器的生命周期关联,在Spring容器启动时启动调度器,而在Spring容器关闭时,停止调度器。所以在Spring中通过这两个FactoryBean配置调度器,再从Spring IoC中获取调度器引用进行任务调度将不会出现这种Web容器关闭而任务依然运行的问题。而如果你在程序中直接使用Timer或Scheduler,如不进行额外的处理,将会出现这一问题。 

    小结 

    开发任务调度的应用程序并非象想象中那样简单,在实际应用中,我们需要考虑到动态任务的产生机制,以照顾数据库性能、任务队列长度、任务执行时间点精度度等方方面面的问题。此外,对于需要集群部署的Web应用程序,我们应该将业务应用和任务调度应用分别部署,以避免一个任务多次执行的问题。Spring已经为我们提供了Quartz和JDK Timer和Spring生命周期关联的FactoryBean,我们应该尽量避免在Web应用中直接手工创建Timer或Scheduler,因为Web容器关闭时,这些调度器本身的线程并不会因此而停止。


0
相关文章