Spring任务调度
任务调度是大多数应用系统的常见需求之一,拿论坛来说:每个半个小时生成精华文章的RSS文件,每天凌晨统计论坛用户的积分排名,每隔30分钟执行对锁定过期的用户进行解锁。以上都是以时间为关注点的调度,事实上我们在实际中还会使用资源上的调度,如线程的使用。spring提供了Quartz,Timer,Executor的支持,使得使用时更加简化。
一、Quartz
1.Quartz提供了强大的任务调度机制,提出了调度器、任务、触发器这三个核心概念。
Job: 是一个接口,只有一个执行方法,开发者想要完成什么任务,可以自己实现。
JobDetail: 描述Job实现类及其他静态信息,Quartz每次执行job时,都是创建一个job实例。
Trigger: 其来触发Job执行的时间触发规则,主要有SimpleTrigger和CronTrigger两个子类。
Scheduler: Quartz运行的一个独立容器。Trigger和JobDetail可以注册进来,其允许外部通过接口方法来访问它们。 通过SchedulerFactory创建一个Scheduler实例,其拥有一个SchedulerContext,保存上下文信息。
任务的信息保存在JobDataMap实例中,Job有个StatefulJob子接口,对于该有状态的任务,任务对JobDataMap的更改会保存下来影响后续的执行,因此不能并发执行。而无状态的job可以并发执行。如图所示:
默认情况下,Quartz的运行信息是保存在内存中的,因为内存中的数据访问最快。如果需要持久化任务调度信息,Quartz允许用户通过调整其属性文件,将信息保存到数据库中。
2.spring中的Quartz
spring为创建Quartz中的Scheduler、Trigger和JobDetail提供了变量的FactoryBean类,首先看实例配置:
<bean id="myService" class="org.slob.service.MyService"></bean>
<bean id="quartzTask" class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean">
<property name="targetObject">
<ref bean="myService" />
</property>
<property name="targetMethod">
<value>quartzJob</value>
</property>
</bean>
<bean id="quartzTaskTrigger" class="org.springframework.scheduling.quartz.CronTriggerBean">
<property name="jobDetail">
<ref bean="quartzTask" />
</property>
<property name="cronExpression">
<value>${cron.expression}</value>
</property>
</bean>
<bean id="startQuertz" lazy-init="false" autowire="no" class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
<property name="triggers">
<list>
<ref bean="quartzTaskTrigger" />
</list>
</property>
</bean>
在实例中我们周期执行的是myService中的quartzJob方法,执行的周期由${cron.expression}来指定。在此trigger为CronTriggerBean,然后开启调度器startQuartz。
下面介绍cron expression的格式:
一个cron表达式有至少6个(也可能7个)有空格分隔的时间元素,从左到右为:
0-59 0-59 0-23 1-31 1-12(*) 1-7(*) 1970-2099
每个元素都显示规定一个值,一个区间 " - " ,一个列表或通配符;一个问号表示不想设置该字段(在星期和日期中使用),“/"字符用于指定增量,如如:“0/15”在秒域意思是每分钟的0,15,30和45秒。其中"L" 在月字段表示该月的最后一天,在周字段表示SAT或7,;"W"只会在日期中出现表示工作日;"#" 只会在星期字段中出现,用于指定本月的某某天,如“6#3”表示本月第三周的星期五(6表示星期五,3表示第三周);"C "在日期和星期字段中出现,这个表达式的值依赖于相关的“日历”的计算结果,如果没有“日历”关联,则等价于所有包含的“日历”。如:日期域是“5C”表示关联“日历”中第一天,或者这个月开始的第一天的后5天。星期域是“1C”表示关联“日历”中第一天,或者星期的第一天的后1天,也就是周日的后一天(周一)。
如表达式:
“10 */1 * * * ?”意为:从10秒开始,每1分钟执行一次
"0 0/5 14,18 * * ?" 在每天下午2点到2:55期间和下午6点到6:55期间的每5分钟触发
"0 10,44 14 ? 3 WED" 每年三月的星期三的下午2:10和2:44触发
"0 15 10 L * ?" 每月最后一日的上午10:15触发
"0 15 10 ? * 6L 2002-2005" 2002年至2005年的每月的最后一个星期五上午10:15触发
"0 15 10 ? * 6#3" 每月的第三个星期五上午10:15触发
二、Java Timer
1.Java通过java.util.Timer和java.util.TimerTask这两个类提供了简单的任务调度功能,允许按照固定频率重复执行某项任务,其只适合对执行时间非常短的任务进行调度,因为TimerTask都在同一背景线程中执行,长时间的任务会严重影响Timer的调度工作。因为如果一个TimerTask的执行占用了过多的时间,后面的任务就会受到影响,在调度时间轴上受到了“挤压”,可能会造成“扎堆”执行的情况。
TimerTask实现了Runnable接口, 我们自己在run()方法中定义任务逻辑,Timer负责制定规则并调度TimerTask。
2.spring中的Timer
spring提供了JDK Timer支持,更加方便使用timer,实力配置如下:
<bean id="myService" class="org.slob.service.MyService"/>
<property name="targetObject">
<ref bean="myService"/>
</property>
<property name="targetMethod">
<value>timerJob</value>
</property>
</bean>
<bean id="scheduleTask" class="org.springframework.scheduling.timer.ScheduledTimerTask">
<property name="timerTask" ref="timerTask" />
<property name="delay">
<value>300000</value>
</property>
<property name="period">
<value>86400000</value>
</property>
</bean>
<bean class="org.springframework.scheduling.timer.TimerFactoryBean">
<property name="scheduledTimerTasks">
<list>
<ref bean="scheduleTask"/>
</list>
</property>
</bean>
该配置在延迟5分钟后,每个24小时执行一次myService中的timerJob方法,如果更加细致的调度,则timer显得力不从心!
三、Jdk Executor
1. JDK5.0中的java.util.concurrent包,提供了功能强大、更高层次的线程构造器。执行器Executor是并发工具包中的一个重要的类,它对Runnable实例的执行进行了抽象,实现者可以提供具体的实现:如简单的以一个线程来运行Runnable,或者通过一个线程池为Runnable提供共享线程。其将“任务提交”和“任务执行”两者分离解耦。
Executor只有一个方法:void execute(Runnable command),接收实现了Runnable的实例,代表了一个待执行的任务。Executor接口还有两个子接口:ExecutorService和SchedulerExecutorService,前者添加了结束任务的管理方法,后者可以对任务进行调度。
JDK ThreadPoolExecutor类实现了Executor和ExecutorService接口,其使用线程池对任务进行调度。 创建线程的开销较大,因此最好能重用同一个线程,动态创建线程池中的线程,防止资源消耗引发的系统性能问题。ThreadPoolExecutor的子类ScheduledThreadPoolExecutor实现ScheduledExecutorService接口,添加了对人物的调度功能。
java.util.concurrent中提供了一个综合性的工厂类Executors,它拥有很多静态工厂方法:
public static ExecutorService newFixedThreadPool(int nThreads):创建一个线程池,重复使用一个固定的线程运行一个共享的无界队列。
public static ExecutorService newCachedThreadPool():线程池是动态的,不够用时创建新的线程,长时间不用的线程将被回收。
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize,ThreadFactory threadFactory):创建一个线程池,可在指定延迟后运行或者定期地执行。
2.spring与executor
spring的发现包中预定义了一些TaskExecutor实现。org.springframework.core.task.TaskExecutor接口的实现类很多,且有一个子接口SchedulingTaskExecutor。
SyncTaskExecutor:该实现不会异步执行任务,每次调用都在发起调用的主线程中。
下面是SchedulingTaskExecutor的实现类:
SimpleAsynTaskExecutor:这个实现没有实现使用线程池,在每次执行任务时都创建一个新线程,支持对并发总数设限
ConcurrentTaskExecutor:该类是JDK Executor的适配器,以便将JDK Executor当成spring的TaskExecutor使用
SimpleThreadPoolTaskExecutor:其是SimpleThreadPool的子类,监听spring的生命周期回调,当用户有线程池,需要Quartz和非Quartz组件中共用时,其发挥作用。
ThreadPoolTaskExecutor:其只可在JDK 5.0中使用,方便spring中配置一个ThreadPoolExecutor,并把它包装成TaskExecutor
TimerTaskExecutor:该类使用一个Timer作为其后台的实现。