基于Spring的任务调度(5)

3 任务调度时需考虑的因素

       如果你打算在应用程序中增加任务调度能力,那在你选择使用何种调度器以及何种调度方式时,你应该考量一些因素。
3.1 选择一个调度器

      你打算在应用程序中增加任务调度能力时,你所做的第一个决定是选择何种调度器。实际上这是一个很容易的选择。如果你只有简单地调度需求或存在不可以在程序中使用第三方库的限制,你应该使用基于Timer的任务调度。否则,使用Quartz。 即使你发现需求很简单,你还是使用Quartz,特别是你需要建立一个显式的Job实现时。使用此种方法,在你的需求变得更高级并且开始使用的是Quartz时,你可以很容易地增加持久性、事务性或更加复杂的触发器而无需修改Job相关的TimerTask。通常来说,我们发现使用Quartz将让我们对某种调度方式变得熟悉。既然有一种方法可以提供我们需要的所有功能,那我们开发人员就不用在两种不同方式之间左右为难了。


3.2 剥离Job类中的任务逻辑

      我们看到很多开发商将调度功能增加到一个应用程序时,所选择的常见做法就是把业务逻辑放在Job类或TimerTask类中。这通常是一个糟糕的做法。在大多数情况下,你需要依照需求来调度任务的执行,这就需要将任务逻辑从调度框架中剥离出来。 同时,你也没必要让你的业务逻辑和调度程序过度耦合。一个更好的方式是将业务逻辑放在一个独立的类中,然后为该类创建一个特定于你所选择的调度程序的简单包装器,也许更好的做法是使用合适的MethodInvoker*FactoryBean为你创建包装器。

 

3.3 任务执行和线程池

      到目前为止,已经讨论了各种各样的任务调度方式,它们可以在指定时间点、指定时间间隔或指定时间点和时间间隔的结合来控制任务的执行。现在,我们将了解Spring中任务调度的另一种方式,它更少依赖时间点或时间间隔,而是即时或者事件触发任务执行。
      例如,考虑一个Web服务器处理接踵而来的请求。建立这样服务器程序的简单方法是将每一个任务放在独立的线程中。这个可能工作绝对正常,但依赖于你正在构建的服务器及其环境。因为线程的创建需要时间和系统资源,所以你可能需要花费比任务执行更多的时间创建和销毁线程,甚至可能耗尽系统资源作为结束。为了稳定运行,服务器需要某些方式管理同时执行的任务。线程池和工作队列的概念正是用于处理这种情况。

      1. java.util.concurrent包

       Java 5中一个令人欣喜的特性是增加了基于Doug Lea的util.concurrent包的java.util.concurrent包,它是提供高效并经良好测试的工具库,用来简化多线程应用程序的开发。该包提供Executor接口,它只定义一个execute(Runnable command)方法来执行Runnable任务。它从任务运行细节中抽象了任务提交。接口的实现提供了所有执行策略的排序方法:每任务一个线程、线程池或者同步执行命名的一些方法(你可以在Executor接口的Javadoc中发现上述方法的一些实现)。

      为了给你一个小示例来演示如何使用Executor接口和子接口ExecutorService,我们先创建一个执行任务,该任务使用先前例1中HelloWorldTask的修补版本。我们使用HelloWorldTask的方式很简单,因为它扩展了实现Runnable接口的TimerTask类,但是我们不能看到使用不同Executor实现间的任务调度的不同之处。见代码例26。

例26

 

package cn.hurraysoft.Executor;

public class HelloWorldCountDownTask implements Runnable {
	private String name;
	private int count=4;
	
	public HelloWorldCountDownTask(String name) {
		this.name=name;
	}
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	@Override
	public void run() {
		while(count>0){
			count --;
			if(count==0){
				System.out.println(name+"sys's helloworld");
			}else{
				System.out.println(name+":"+count);
				Thread.yield();
			}
		}
	}

}

 

      HelloWorldCountDownTask类 任务做的事情是打印出从3开始的倒计数,接着调用Thread.yield()方法来使这个线程的执行暂停,这时其他线程可以被执行。作为一条语句,任务向世界说了一声"Hello World!"结束了执行。

     接下来,正如例27所展示的,我们将使用ExecutorService实现调度和执行这个任务。

例27 

 

package cn.hurraysoft.test;

import java.io.IOException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.context.support.FileSystemXmlApplicationContext;

import cn.hurraysoft.Executor.HelloWorldCountDownTask;

public class ExecutorServiceExample {
	public static void main(String[] args) throws IOException {
		ExecutorService executorService =Executors.newFixedThreadPool(2);
		executorService.execute(new HelloWorldCountDownTask("Anna"));
		executorService.execute(new HelloWorldCountDownTask("Beth"));
		executorService.execute(new HelloWorldCountDownTask("Charlie"));
		executorService.execute(new HelloWorldCountDownTask("Daniel"));
		executorService.shutdown();
	}
}
 

 

      使用ExecutorService调度执行任务 java.util.concurrent.Executors类为Executor和ExecutorService类提供了便利的工厂和工具方法。我们使用newFixedThreadPool()方法获得具有线程池的两个线程的ThreadPoolExecutor实例。然后我们提交了4个任务执行,并在所有任务执行结束后调用ExecutorService的shutdown()方法关闭ExecutorService。调用此方法以后,没有更多的任务增加到服务中。运行示例将打印输出如下信息:

 

Anna:3
Beth:3
Anna:2
Beth:2
Anna:1
Beth:1
Annasys's helloworld
Charlie:3
Bethsys's helloworld
Daniel:3
Charlie:2
Daniel:2
Charlie:1
Daniel:1
Charliesys's helloworld
Danielsys's helloworld
 

 

      注意这只同时执行两个任务,任务Charlie只在任务Anna执行完以后才被执行。你可以尝试线程池中不同数量的线程或不同Executor实现,会发现打印输出也会不同。

      2. Spring的TaskExecutor接口

      从2.0版开始,Spring就提供了先前讨论的Java 5 Executor框架的一个抽象接口。与java.util. concurrent.Executor一样,TaskExecutor接口只定义了一个execute(Runnable command)方法。它也可以在其他Spring组件内部使用,例如同步JMS和JCA环境支持,现在你无需使用Java 5就可以向自己的应用程序增加线程池特性。 Spring提供了各种TaskExecutor的实现,见表3中的说明。

 

表3 Spring的TaskExecutor实现

实现

说明

SimpleAsyncTaskExecutor

此实现提供异步线程,每次调用

都新建一个线程。它也可以设置并

发总数限制以阻塞更多新的调用

SyncTaskExecutor

当我们选用该实现调度任务时,

在调用线程中的执行会同步发生

ConcurrentTaskExecutor

该类实现了SpringScheduling

TaskExecutor接口和

Java5java.util. concurrent.

Executor接口,作为后者的封装类

SimpleThreadPoolTaskExecutor

此实现是QuartzSimpleThreadPool

类的子类,当线程池需要在Quartz和非

Quartz组件间共享时非常有用

ThreadPoolTaskExecutor

此实现的使用方式和ConcurrentTas

kExecutor实现相似,它开放的bean

属性可以用来配置java.util.concurrent.

ThreadPoolExecutor(需要Java 5支持)

TimerTaskExecutor

这个实现使用一个TimerTask作为

后台的实现。在独立线程中调用,

但在那个线程中是同步的

WorkManagerTaskExecutor

此实现使用CommonJ WorkManager

作为底层实现,并实现了WorkManager接口

      各种实现方式的不同之处在小示例中很容易看出来。
      例28展示了Spring中3个TaskManager实现的配置:ThreadPoolTaskExecutor、SyncTaskExecutor和TimerTaskExecutor。
  例28 ApplicationContext_4.xml文件
<?xml version="1.0" encoding="UTF-8"?>
<beans
	xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd">
	<bean id="SynchTaskExecutor" class="org.springframework.core.task.SyncTaskExecutor"></bean>
	<bean id="threadPoolTaskExecutor" class="org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor">
		<property name="corePooSize" value="5"/>
		<property name="maxPooSize" value="10"/>
		<property name="queueCapacity" value="25"/>
	</bean>
	<bean id="timerTaskExcutor" class="org.springframework.scheduling.timer.TimerTaskExecutor">
		<property name="delay" value="3000"></property>
		<property name="timer" value="timer"></property>
	</bean>
	<bean id="timer" class="java.util.Timer"></bean>
	
	<bean id="TaskExecutorExample" class="cn.hurraysoft.Executor.TaskExecutorExample">
		<property name="taskExecutor" ref="SynchTaskExecutor"></property>
	</bean>
</beans>
 
      然后我们可以使用已定义的bean从ApplicationContext中装载它们,并在一个TaskExecutor中使用,如例29所示。
例29 TaskExecutorExample类
package cn.hurraysoft.Executor;

import org.springframework.core.task.TaskExecutor;

public class TaskExecutorExample {
	private TaskExecutor taskExecutor;

	public TaskExecutor getTaskExecutor() {
		return taskExecutor;
	}

	public void setTaskExecutor(TaskExecutor taskExecutor) {
		this.taskExecutor = taskExecutor;
	}
	public void executeTask(){
		this.taskExecutor.execute(new HelloWorldCountDownTask("Anna"));
		this.taskExecutor.execute(new HelloWorldCountDownTask("Beth"));
		this.taskExecutor.execute(new HelloWorldCountDownTask("Charlie"));
		this.taskExecutor.execute(new HelloWorldCountDownTask("Daniel"));
	}
	
}
 
      如你所见,我们使用和前面一样的4个HelloWorldCountDownTask实例。生成的输出强调了执行策略间的不同之处。如预期所料,如例30的SyncTaskExecutorExample类同步执行了任务。
例30 SyncTaskExecutorExample类
package cn.hurraysoft.test;

import java.io.IOException;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.context.support.FileSystemXmlApplicationContext;
import org.springframework.core.task.TaskExecutor;

import cn.hurraysoft.Executor.TaskExecutorExample;

public class SyncTaskExecutorExample{
	public static void main(String[] args) throws IOException {
		ApplicationContext ctx=new ClassPathXmlApplicationContext(
				"applicationContext_4.xml");
		TaskExecutorExample taskExecuteExample= (TaskExecutorExample) ctx.getBean("TaskExecutorExample");
		taskExecuteExample.executeTask();
		
	}
}
 
      运行代码,将产生和下面类似的输出:
Anna:3
Anna:2
Anna:1
Annasys's helloworld
Beth:3
Beth:2
Beth:1
Bethsys's helloworld
Charlie:3
Charlie:2
Charlie:1
Charliesys's helloworld
Daniel:3
Daniel:2
Daniel:1
Danielsys's helloworld
 
      如果你的应用程序运行在Java 5或更高版本上,那么你可以配置此示例运行在任何Java 5规范的实现上。Spring的ThreadPoolTaskExecutor让你能够配置JDK 1.5的ThreadPoolExecutor的bean属性,作为一个Spring TaskExecutor来公开。另外,Spring为其他的Java 5 Executor实现提供了作为适配器类的ConcurrentTaskExecutor实现,它让从Java 1.4升级更容易。
      适配器类实现了TaskExecutor和Executor两个接口。因为主要接口是TaskExecutor,异常处理遵循它的契约。例如,当任务执行不可以接受时,一个Spring TaskRejectedException异常将被抛出,而不会抛出java.util.concurrent.RejectedExecutionException异常。
      TaskExecutor接口的一个更便利特性是它对运行时异常的包装。在一个任务失败抛出异常时,这种情况通常认为是致命的。若没有必要或没有可能修复,异常可能是非检查类型的,你的代码保持更轻量级,可以很容易地在各种TaskExecutor实现间切换。

3.4 小结
      本章介绍了Spring中集成的各种任务调度机制。我们先讨论在使用JDK Timer时得到的基本任务调度支持和使用Quartz得到的更高级的支持。也学习了如何使用不同类型的触发器。另外,我们特别讨论了Quartz中的CronTrigger,它是一种创建符合现实生活的复杂调度计划的方法。 任务调度是企业级应用程序的一个重要组成部分,Spring提供了极好的支持来帮助你将调度功能增加到应用中。

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值