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

Spring中的定时调度(Scheduling)和线程池(Thread Pooling)

2012-12-23 00:15 363 查看

出处:http://blog.sina.com.cn/s/blog_81412c180101etxr.html

23.1.简介

Spring包含了对定时调度服务的内置支持类。当前,Spring支持从JDK1.3开始内置的Timer类和QuartzScheduler(http://www.opensymphony.com/quartz/)。二者都可以通过FactoryBean,分别指向Timer或Trigger实例的引用进行配置。更进一步,有个对Quartz
Scheduler和Timer都有效的工具类可以让你调用某个目标对象的方法(类似通常的MethodInvokingFactoryBean操作)。Spring还包含有用于线程池调度的类,它针对Java1.3,1.4,5和JEE环境的差异都进行了抽象。

23.2.使用OpenSymphonyQuartz调度器

Quartz使用Trigger,Job以及JobDetail等对象来进行各种类型的任务调度。关于Quartz的基本概念,请参阅http://www.opensymphony.com/quartz。为了让基于Spring的应用程序方便使用,Spring提供了一些类来简化uartz的用法。

23.2.1.使用JobDetailBean

JobDetail对象保存运行一个任务所需的全部信息。Spring提供一个叫作JobDetailBean的类让JobDetail能对一些有意义的初始值进行初始化。让我们来看个例子:


Jobdetailbean拥有所有运行job(ExampleJob)的必要信息。通过job的datamap来制定timeout。Job的datamap可以通过JobExecutionContext(在运行时刻传递给你)来得到,但是JobDetailBean同时把从job的datamap中得到的属性映射到实际job中的属性中去。所以,如果ExampleJob中包含一个名为timeout的属性,JobDetailBean将自动为它赋值:
packageexample;publicclassExampleJobextendsQuartzJobBean{privateinttimeout;publicvoidsetTimeout(inttimeout){this.timeout=timeout;}protectedvoidexecuteInternal(JobExecutionContextctx)throwsJobExecutionException{//dotheactualwork}}


当然,你同样可以对Jobdetailbean中所有其他的额外配置进行设置。

注意:使用name和group属性,你可以分别修改job在哪一个组下运行和使用什么名称。默认情况下,job的名称等于jobdetailbean的名称(在上面的例子中为exampleJob)。

23.2.2.使用MethodInvokingJobDetailFactoryBean

通常情况下,你只需要调用特定对象上的一个方法即可实现任务调度。你可以使用MethodInvokingJobDetailFactoryBean准确的做到这一点:


上面例子将调用exampleBusinessObject中的doIt方法(如下):
publicclassExampleBusinessObject{//propertiesandcollaboratorspublicvoiddoIt(){//dotheactualwork}}



使用MethodInvokingJobDetailFactoryBean你不需要创建只有一行代码且只调用一个方法的job,你只需要创建真实的业务对象来包装具体的细节的对象。

默认情况下,QuartzJobs是无状态的,可能导致jobs之间互相的影响。如果你为相同的JobDetail指定两个Trigger,很可能当第一个job完成之前,第二个job就开始了。如果JobDetail对象实现了Stateful接口,就不会发生这样的事情。第二个job将不会在第一个job完成之前开始。为了使得jobs不并发运行,设置MethodInvokingJobDetailFactoryBean中的concurrent标记为false。



Note
注意:默认情况下,jobs在并行的方式下运行。

23.2.3.使用triggers和SchedulerFactoryBean来包装任务

我们已经创建了jobdetails,jobs。我们同时回顾了允许你调用特定对象上某一个方法的便捷的bean。当然我们仍需要调度这些jobs。这需要使用triggers和SchedulerFactoryBean来完成。Quartz自带一些可供使用的triggers。Spring提供两个子类triggers,分别为CronTriggerBean和SimpleTriggerBean。

Triggers也需要被调度。Spring提供SchedulerFactoryBean来暴露一些属性来设置triggers。SchedulerFactoryBean负责调度那些实际的triggers。

几个例子:


现在我们创建了两个triggers,其中一个开始延迟10秒以后每50秒运行一次,另一个每天早上6点钟运行。我们需要创建一个SchedulerFactoryBean来最终实现上述的一切:


更多的属性你可以通过SchedulerFactoryBean来设置,例如jobdetails使用的Calendars,用来订制Quartz的一些属性以及其它相关信息。你可以查阅相应的JavaDOC(http://www.springframework.org/docs/api/org/springframework/scheduling/quartz/SchedulerFactoryBean.html)来了解进一步的信息。

23.3.使用JDKTimer支持类

另外一个调度任务的途径是使用JDKTimer对象。更多的关于Timer的信息可以在这里http://java.sun.com/docs/books/tutorial/essential/threads/timer.html找到。
上面讨论的概念仍可以应用于Timer的支持。你可以创建定制的timer或者调用某些方法的timer。包装timers的工作由TimerFactoryBean完成。

23.3.1.创建定制的timers

你可以使用TimerTask创建定制的timertasks,类似于Quartz中的jobs:
publicclassCheckEmailAddressesextendsTimerTask{privateListemailAddresses;publicvoidsetEmailAddresses(ListemailAddresses){this.emailAddresses=emailAddresses;}publicvoidrun(){//iterateoverallemailaddressesandarchivethem}}


包装它很简单:
test@springframework.orgfoo@bar.comjohn@doe.net


注意若要让任务只运行一次,你可以把period属性设置为0(或者负值)。

23.3.2.使用MethodInvokingTimerTaskFactoryBean类

和对Quartz的支持类似,对Timer的支持也包含一个组件,可以让你周期性的调用某个方法:


以上的例子会调用exampleBusinessObject对象的doIt方法。(见下):
publicclassBusinessObject{//propertiesandcollaboratorspublicvoiddoIt(){//dotheactualwork}}


将上例中ScheduledTimerTask的timerTask引用修改为doIt,bean将会用一个固定的周期来调用doIt方法。

23.3.3.打包:使用TimerFactoryBean来设置任务

TimerFactoryBean类和Quartz的SchedulerFactoryBean类有些类似,它们是为同样的目的而设计的:设置确切的任务计划。TimerFactoryBean对一个Timer进行配置,设置其引用的任务的周期。你可以指定是否使用背景线程。


23.4.SpringTaskExecutor抽象

Spring2.0为执行器(Executor)处理引入了一个新的抽象层。Executor是Java5的名词,用来表示线程池的概念。之所以用这个奇怪的名词,是因为实际上不能保证底层实现的确是一个池。实际上,很多情况下,executor只是单线程。Spring的抽象层帮助你把线程池引入到Java1.3和1.4环境中,同时隐藏了1.3,1.4,5,和JavaEE环境中线程池实现的差异。

23.4.1.TaskExecutor接口

Spring的TaskExecutor接口等同于java.util.concurrent.Executor接口。实际上,它存在主要原因是为了在使用线程池的时候,将对Java5的依赖抽象掉。这个接口只有一个方法execute(Runnabletask),它根据线程池的语义和配置,来接受一个执行任务。

23.4.2.何时使用TaskExecutor接口

TaskExecutor接口开始的时候,是为了其他Spring组件使用线程池的抽象需要创建的。例如ApplicationEventMulticaster组件、JMS的AbstractMessageListenerContainer和对Quartz的整合都使用了TaskExecutor接口来抽象线程池。当然,如果你的bean需要线程池行为,你也可以使用这个抽象层。

23.4.3.TaskExecutor类型

在Spring发行包中预定义了一些TaskExecutor实现。有了它们,你甚至不需要再自行实现了。

SimpleAsyncTaskExecutor类

这个实现不重用任何线程,或者说它每次调用都启动一个新线程。但是,它还是支持对并发总数设限,当超过线程并发总数限制时,阻塞新的调用,直到有位置被释放。如果你需要真正的池,请继续往下看。

SyncTaskExecutor类

这个实现不会异步执行。相反,每次调用都在发起调用的线程中执行。它的主要用处是在不需要多线程的时候,比如简单的testcase。

ConcurrentTaskExecutor类

这个实现是对Java5java.util.concurrent.Executor类的包装。有另一个备选,ThreadPoolTaskExecutor类,它暴露了Executor的配置参数作为bean属性。很少需要使用ConcurrentTaskExecutor,但是如果ThreadPoolTaskExecutor不敷所需,ConcurrentTaskExecutor是另外一个备选。

SimpleThreadPoolTaskExecutor类

这个实现实际上是Quartz的SimpleThreadPool类的子类,它会监听Spring的生命周期回调。当你有线程池,需要在Quartz和非Quartz组件中共用时,这是它的典型用处。

ThreadPoolTaskExecutor类

它不支持任何对java.util.concurrent包的替换或者下行移植。DougLea和DawidKurzyniec对java.util.concurrent的实现都采用了不同的包结构,导致它们无法正确运行。

这个实现只能在Java5环境中使用,但是却是这个环境中最常用的。它暴露的beanproperties可以用来配置一个java.util.concurrent.ThreadPoolExecutor,把它包装到一个TaskExecutor中。如果你需要更加先进的类,比如ScheduledThreadPoolExecutor,我们建议你使用ConcurrentTaskExecutor来替代。

TimerTaskExecutor类

这个实现使用一个TimerTask作为其背后的实现。它和SyncTaskExecutor的不同在于,方法调用是在一个独立的线程中进行的,虽然在那个线程中是同步的。

WorkManagerTaskExecutor类

CommonJ是BEA和IBM联合开发的一套规范。这些规范并非JavaEE的标准,但它是BEA和IBM的应用服务器实现的共同标准

这个实现使用了CommonJWorkManager作为其底层实现,是在Springcontext中配置CommonJWorkManager应用的最重要的类。和SimpleThreadPoolTaskExecutor类似,这个类实现了WorkManager接口,因此可以直接作为WorkManager使用。

23.4.4.使用TaskExecutor接口

Spring的TaskExecutor实现作为一个简单的JavaBeans使用。在下面的示例中,我们定义一个bean,使用ThreadPoolTaskExecutor来异步打印出一系列字符串。
importorg.springframework.core.task.TaskExecutor;publicclassTaskExecutorExample{privateclassMessagePrinterTaskimplementsRunnable{privateStringmessage;publicMessagePrinterTask(Stringmessage){this.message=message;}publicvoidrun(){System.out.println(message);}}privateTaskExecutortaskExecutor;publicTaskExecutorExample(TaskExecutortaskExecutor){this.taskExecutor=taskExecutor;}publicvoidprintMessages(){for(inti=0;i<25;i++){taskExecutor.execute(newMessagePrinterTask("Message"+i));}}}


可以看到,无需你自己从池中获取一个线程来执行,你把自己的Runnable类加入到队列中去,TaskExecutor使用它自己的内置规则来决定何时应该执行任务。

为了配置TaskExecutor使用的规则,暴露了简单的beanproperties。


最后附上cronExpression格式说明

字段允许值允许的特殊字符

秒0-59,-*/

分0-59,-*/

小时0-23,-*/

日期1-31,-*?/LWC

月份1-12或者JAN-DEC,-*/

星期1-7或者SUN-SAT,-*?/LC#

年(可选)留空,1970-2099,-*/

“-”字符被用来指定一个范围。

“*”字符被用来指定所有的值。

“?”字符只在日期域和星期域中使用。它被用来指定“非明确的值”。

“,”字符被用来指定另外的值。Forexample"MON,WED,FRI"intheday-of-weekfieldmeans"thedaysMonday,Wednesday,andFriday".

L("last")例如:用在day-of-month字段意思是"这个月最后一天";

#第几个。

/符号前表示开始时间,符号后表示每次递增的值;

C指和calendar联系后计算过的值。例:在day-of-month字段用“5C”指在这个月第5天或之后包括calendar的第一天;在day-of-week字段用“1C”指在这周日或之后包括calendar的第一天。

W("weekday")只能用在day-of-month字段。用来描叙最接近指定天的工作日(周一到周五)。例如:在day-of-month字段用“15W”指“最接近这个月第15天的工作日”,即如果这个月第15天是周六,那么触发器将会在这个月第14天即周五触发;如果这个月第15天是周日,那么触发器将会在这个月第16天即周一触发;如果这个月第15天是周二,那么就在触发器这天触发。注意一点:这个用法只会在当前月计算值,不会越过当前月。“W”字符仅能在day-of-month指明一天,不能是一个范围或列表。也可以用“LW”来指定这个月的最后一个工作日。

例子:

"0012**?"每天中午12点触发

"01510?**"每天上午10:15触发

"01510**?"每天上午10:15触发

"01510**?*"每天上午10:15触发

"01510**?2005"2005年的每天上午10:15触发

"0*14**?"在每天下午2点到下午2:59期间的每1分钟触发

"00/514**?"在每天下午2点到下午2:55期间的每5分钟触发

"00/514,18**?"在每天下午2点到2:55期间和下午6点到6:55期间的每5分钟触发

"00-514**?"在每天下午2点到下午2:05期间的每1分钟触发

"010,4414?3WED"每年三月的星期三的下午2:10和2:44触发

"01510?*MON-FRI"周一至周五的上午10:15触发

"0151015*?"每月15日上午10:15触发

"01510L*?"每月最后一日的上午10:15触发

"01510?*6L"每月的最后一个星期五上午10:15触发

"01510?*6L2002-2005"2002年至2005年的每月的最后一个星期五上午10:15触发

"01510?*6#3"每月的第三个星期五上午10:15触发
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: