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 | 该类实现了Spring的Scheduling TaskExecutor接口和 Java5的java.util. concurrent. Executor接口,作为后者的封装类 |
SimpleThreadPoolTaskExecutor | 此实现是Quartz的SimpleThreadPool 类的子类,当线程池需要在Quartz和非 Quartz组件间共享时非常有用 |
ThreadPoolTaskExecutor | 此实现的使用方式和ConcurrentTas kExecutor实现相似,它开放的bean 属性可以用来配置java.util.concurrent. ThreadPoolExecutor(需要Java 5支持) |
TimerTaskExecutor | 这个实现使用一个TimerTask作为 后台的实现。在独立线程中调用, 但在那个线程中是同步的 |
WorkManagerTaskExecutor | 此实现使用CommonJ WorkManager 作为底层实现,并实现了WorkManager接口 |
<?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>
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"));
}
}
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