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接口
|
各种实现方式的不同之处在小示例中很容易看出来。
例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提供了极好的支持来帮助你将调度功能增加到应用中。
分享到:
相关推荐
改分布式任务调度特性如下: 1、简单:支持通过Web页面对任务进行CRUD操作,操作简单,一分钟上手; 2、动态:支持动态修改任务状态、暂停/恢复任务,以及终止运行中任务,即时生效; 3、调度中心HA(中心式):调度...
本源码为基于Spring和Quartz的任务调度监控管理平台设计,共包含553个文件,其中css文件190个,png文件111个,java文件96个,js文件63个,sql文件22个,html文件20个,jsp文件17个,gif文件15个,xml文件9个,...
当然,若要实现更加强大的任务调度功能,可以采用Spring内部集成的Quartz这个开源调度框架; l Spring封装线程池:为了提高任务执行效率,我们必须考虑让任务的具体操作能够被并发执行;为了让系统更加轻量级,这里...
schedule-job, 基于Spring Boot Quartz 的分布式任务调度系统
在线教育平台服务端基于Spring Boot构建,采用Spring Cloud微服务框架。 持久层:MySQL、MongoDB、Redis、ElasticSearch 数据访问层:使用Spring Data JPA 、Mybatis、Spring Data Mongodb等 业务层:Spring IOC、...
基于zookeeper+spring task的分布式任务调度组件,非常小巧,无需任何修改就可以使spring task具备分布式特性,确保所有任务在集群中不重复,不遗漏的执行。 5.Quartz 官方地址:...
"基于Spring Boot框架的任务调度系统的设计与实现"是一门专注于使用Spring Boot实现任务调度功能的课程,旨在帮助学员掌握任务调度系统的设计原理和实际开发技术。以下是该课程的资源描述: ### 课程内容概述: 该...
基于quartz的任务调度插件,引入到spring项目即可使用,依赖于redis的订阅可以对任务立即运行,修改任务自动执行时间/频率,暂停等操作。适合需要定时任务的项目进行快速的集成开发,提高开发效率,降低开发难度。
SSM框架(spring+springMVC +mybatis) +任务调度管理,,经过本人亲自测试,程序没有任何问题!
集成了分布式任务调度框架 Quartz ,任务存储于数据库。 使用SpringMVC作为路由控制, 集成 Swagger2 提供实时 RESTful API文档。 数据持久层集成 Mybatis 框架。 使用自定义注解 @TargetDataSource 实现了多数据源...
SingleJDBCBase 是基于Spring Framework基础上搭建的一个Java基础开发套件,以Spring MVC为模型视图控制器,JDBC为数据访问层。 * 核心框架:Spring Framework 4.2.7 * 安全框架: * 视图框架:Spring MVC 4.2.7 * ...
elastic job是当当网开源的基于quartz的分布式调度框架,通过zookeeper实 现分布式协调,加上支持分片、日志追踪、任务管理UI、高可性被大家熟知。 2.目前新的项目基本都是spring boot,如何通过约束、配置方式...
基于springboot的任务调度技术quartz简单的实现demo,根目录有数据表,需要配置到您自己的数据库中,之后点击运行即可,比较有趣的是其中会用到一些反射知识,还有难点就是要在调度任务执行的类上@service添加类的...
基于spring+mybatis的quartz计划任务调度应用。支持mysql,oracle多种数据库。
在线教育平台服务端基于Spring Boot构建,采用Spring Cloud微服务框架。 持久层:MySQL、MongoDB、Redis、ElasticSearch 数据访问层:使用Spring Data JPA 、Mybatis、Spring Data Mongodb等 业务层:Spring IOC...
@Scheduled注解的实现原理主要依赖于Spring框架的任务调度机制。当Spring容器启动时,它会扫描所有带有@Scheduled注解的方法,并将它们注册到任务调度器中。任务调度器会根据注解中指定的时间间隔或Cron表达式来触发...
任务调度试系统,基本功能包括:用户的注册、用户的登录、发起项目、项目详细及搜索等。本系统结构如下: (1)用户的注册登录: 注册模块:完成用户注册功能; 登录模块:完成用户登录功能; (2)发起项目: ...
集成了分布式任务调度框架Quartz ,任务存储于数据库。 使用SpringMVC作为路由控制,集成的SWagger2提供实时RESTful API文档。 数据持久层集成Mybatis框架。 使用自定义注释解@TargetDataSource实现了多数据源动态...
任务调度试系统,基本功能包括:用户的注册、用户的登录、发起项目、项目详细及搜索等。本系统结构如下: (1)用户的注册登录: 注册模块:完成用户注册功能; 登录模块:完成用户登录功能; (2)发起项目: ...
基于spring-boot+quartz的CRUD任务管理系统。已实现功能有:任务列表。任务新增和修改 任务执行。表达式生成器。任务移除。Job中注入service为空的问题。系统启动,如果数据库任务为零则初始化测试任务,用于测试