任务调度接口:TaskScheduler
除了TaskExecutor抽象之外,Spring 3.0还引用了任务调度接口 TaskScheduler,它提供了多种方法来调度将来某个时间点要运行的任务。
代码语言:javascript复制public interface TaskScheduler {
ScheduledFuture schedule(Runnable task, Trigger trigger); //通过触发器来决定task是否执行
ScheduledFuture schedule(Runnable task, Date startTime); //在starttime的时候执行一次
ScheduledFuture scheduleAtFixedRate(Runnable task, Date startTime, long period); //从starttime开始每个period时间段执行一次task
ScheduledFuture scheduleAtFixedRate(Runnable task, long period); //每隔period执行一次
ScheduledFuture scheduleWithFixedDelay(Runnable task, Date startTime, long delay); //从startTime开始每隔delay长时间执行一次
ScheduledFuture scheduleWithFixedDelay(Runnable task, long delay); //每隔delay时间执行一次
}
固定速率和固定延迟方法用于简单的、周期性的执行,但是使用 Trigger 的方法要灵活得多。
Trigger接口
TaskScheduler中将会使用到Trigger对象,Trigger接口用于计算任务的下次执行触发时间。通过实现Trigger接口可以实现自定义触发器来执行执行task。
代码语言:javascript复制public interface Trigger {
Date nextExecutionTime(TriggerContext triggerContext);
}
TriggerContext 保存 Trigger 接口任务执行调度的信息。它封装了所有相关的数据,如果需要,将来可以对其进行扩展。TriggerContext是一个接口(默认情况下使用SimpleTriggerContext实现)。在这里,您可以看到哪些方法可用于触发器实现。
代码语言:javascript复制public interface TriggerContext {
Date lastScheduledExecutionTime();
Date lastActualExecutionTime();
Date lastCompletionTime();
}
Trigger实现
Spring也提供了触发器接口的两个默认的实现类:PeriodicTrigger 和 CronTrigger。
PeriodicTrigger
用于定期执行的Trigger。它有两种模式:
- fixedRate:两次任务开始时间之间间隔指定时长
- fixedDelay: 上一次任务的结束时间与下一次任务开始时间“间隔指定时长
默认情况下PeriodicTrigger使用了fixedDelay模式。
CronTrigger
通过Cron表达式来生成调度计划。比如:scheduler.schedule(task, new CronTrigger(“0 15 9-17 * * MON-FRI”)); 表示 “工作日的9点到17点,每个小时的15分执行一次”。
cron表达式含义见《cron表达式》
Spring对cron表达式的支持,是由CronSequenceGenerator来实现的,不依赖于别的框架。下面给出一个Demo感受下:
代码语言:javascript复制public static void main(String[] args) {
CronSequenceGenerator generator = new CronSequenceGenerator("0 15 * * * MON-FRI");
Date next = generator.next(new Date());
System.out.println(next); //Mon Apr 22 17:15:00 CST 2021
System.out.println(generator.next(next)); //Mon Apr 22 18:15:00 CST 2021
}
TaskScheduler接口
Spring任务调度器的核心接口,定义了执行定时任务的主要方法,主要根据任务的不同触发方式调用不同的执行逻辑,其实现类都是对JDK原生的定时器或线程池组件进行包装,并扩展额外的功能。
TaskScheduler实现
与Spring的TaskExecutor抽象一样,TaskScheduler 主要好处是应用程序的调度需求与部署环境解耦,应用程序本身不应该直接创建线程。
TaskScheduler有如下实现类 ConcurrentTaskScheduler、ThreadPoolTaskScheduler:
ConcurrentTaskScheduler
以单个线程方式执行定时任务,适用于简单场景。
代码语言:javascript复制public class testTaskExecutor {
public static void main(String[] args) {
ConcurrentTaskScheduler taskScheduler = new ConcurrentTaskScheduler();
// 执行任务
// 执行一次
taskScheduler.execute(() -> System.out.println(Thread.currentThread().getName() " 我只会被执行一次~~~"));
// 周期性执行
taskScheduler.schedule(() -> System.out.println(Thread.currentThread().getName() " 我会被多次执行~~~"), new CronTrigger("0/2 * * * * ?"));
// 此处:若你有周期性的任务,这里不要shutdown()
//taskScheduler.shutdown();
}
}
执行结果:
执行的线程都是一样的。
ThreadPoolTaskScheduler
代码语言:javascript复制public class ThreadPoolTaskScheduler extends ExecutorConfigurationSupport
implements AsyncListenableTaskExecutor, SchedulingTaskExecutor, TaskScheduler
除实现了TaskScheduler接口中的方法外,它还包含了一些对ScheduledThreadPoolExecutor进行操作的接口,大多数场景下都使用它来进行任务调度。
任务调度Demo
代码语言:javascript复制package TaskSchedulerDemo;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
import org.springframework.scheduling.support.CronTrigger;
public class testTaskExecutor {
public static void main(String[] args) {
ThreadPoolTaskScheduler taskScheduler = new ThreadPoolTaskScheduler();
taskScheduler.setPoolSize(6);
taskScheduler.initialize(); // 务必调用此方法来手动启动
// 执行任务
// 执行一次
taskScheduler.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() " 我只会被执行一次~~~");
}
});
// lambda表达式,多用于匿名内部类、forEach()方法等。小括号()用于传参,大括号{}用于执行相关操作、返回值等。
// taskScheduler.execute(() -> System.out.println(Thread.currentThread().getName() " 我只会被执行一次~~~"));
// 周期性执行
taskScheduler.schedule(() -> System.out.println(Thread.currentThread().getName() " 我会被多次执行~~~"), new CronTrigger("0/2 * * * * ?"));
// 此处:若你有周期性的任务,这里不要shutdown()
//taskScheduler.shutdown();
}
}
执行结果:
注:使用前必须得先调用initialize()【初始化方法】。shutDown()方法执行完后可以关闭线程。
注入方式
1、applicationContext.xml配置文件配置ThreadPoolTaskScheduler:
代码语言:javascript复制<context:component-scan base-package="TaskSchedulerDemo" />
<bean id = "taskScheduler" class="org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler">
<property name="poolSize" value = "5"></property>
</bean>
2、定义一个任务DataSimulation:
代码语言:javascript复制package TaskSchedulerDemo;
import org.springframework.stereotype.Component;
import java.util.Random;
@Component
public class DataSimulation implements Runnable {
@Override
public void run() {
Random random = new Random();
System.out.println("[" Thread.currentThread().getName() "]" "-" random.nextInt(10));
}
}
3、SchedulerFacotory类注入TaskScheduler 对象:
代码语言:javascript复制@Component
public class SchedulerFacotory {
@Autowired
public TaskScheduler scheduler;
public TaskScheduler getScheduler() {
return scheduler;
}
public void setScheduler(TaskScheduler scheduler) {
this.scheduler = scheduler;
}
public void schedulerFactory(){
scheduler.schedule(new DataSimulation(), new CronTrigger("0/2 * * * * ?"));
}
}
4、调用:
代码语言:javascript复制public class testTaskScheduler {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext(new String[] {
"applicationContext.xml"});
SchedulerFacotory sf = (SchedulerFacotory)context.getBean("schedulerFacotory");
sf.schedulerFactory();
}
}
运行结果:
注解方式(自动启动)
1、Spring配置文件applicationContext.xml中注解配置如下:
代码语言:javascript复制<!--添加注解的扫描包-->
<context:component-scan base-package="TaskSchedulerAnnoStartDemo" />
<!--配置注解驱动-->
<task:annotation-driven />
<task:scheduler id="myScheduler" pool-size="5"/>
代码语言:javascript复制<task:annotation-driven />还可以通过scheduler,指定具体的任务调度器。
<!--添加注解的扫描包-->
<context:component-scan base-package="TaskSchedulerAnnoStartDemo" />
<!--配置注解驱动 多个scheduler时,可以指定scheduler-->
<task:annotation-driven scheduler="myScheduler2"/>
<task:scheduler id="myScheduler" pool-size="5"/>
<bean id = "myScheduler2" class="org.springframework.scheduling.concurrent.ConcurrentTaskScheduler">
</bean>
注:<task:scheduler id=“myScheduler” pool-size=“5”/> 默认使用 ThreadPoolTaskScheduler。
2、创建SchedulerPoolService,并在service中使用 @Scheduled 注解创建定时任务
代码语言:javascript复制package TaskSchedulerDemo;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
@Service
public class SchedulerPoolService {
@Scheduled(cron = "0/3 * * * * ?")
public void task1(){
Thread thread = Thread.currentThread();
System.out.println("[" Thread.currentThread().getName() "]" new Date() "-task1-id:" thread.getId() ",group:" thread.getThreadGroup());
}
@Scheduled(fixedDelay = 5000)
public void task2(){
Thread thread = Thread.currentThread();
System.out.println("[" Thread.currentThread().getName() "]" new Date() "-task2-id:" thread.getId() ",group:" thread.getThreadGroup());
}
}
3、加载配置文件即可,不需要手动启动任务。
代码语言:javascript复制public class testTaskExecutor {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext(new String[] {
"TaskSchedulerDemo/applicationContext.xml"});
}
}
运行结果:
完全注解开发(自动启动)
还可以结合配置类 @Configuration,@EnableScheduling 开启配置计划任务,实现完全注解开发,不需要手动启动任务。 配置类SpringConfig:
代码语言:javascript复制@Configuration
@ComponentScan("TaskSchedulerAnnoStartDemo")
@EnableScheduling // 开启配置计划任务
public class SpringConfig {
@Bean
public TaskScheduler getTaskScheduler(){
ThreadPoolTaskScheduler threadPoolTaskScheduler = new ThreadPoolTaskScheduler();
threadPoolTaskScheduler.setPoolSize(5);
return threadPoolTaskScheduler;
}
}
启动类:
代码语言:javascript复制public class testTaskExecutor {
public static void main(String[] args) {
//ApplicationContext context = new ClassPathXmlApplicationContext(new String[] {"TaskSchedulerAnnoStartDemo/applicationContext.xml"});
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class);
}
}
运行结果:
Spring异步执行
Spring默认的事件机制是同步的。举个例子:
代码语言:javascript复制@Service
public class SchedulerPoolService {
@Scheduled(fixedDelay = 3000)
public void task(){
Thread thread = Thread.currentThread();
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("[" Thread.currentThread().getName() "]" "[" new Date() "]" "task-id:" thread.getId() ",group:" thread.getThreadGroup());
}
}
在原先service增加触发时间打印,而且sleep(5000)。
运行结果:
可以看出,任务每8秒执行一次,是轮询秒数(3秒) 单次任务执行时间(5秒),说明任务是同步执行。
Spring为任务调度和异步方法执行提供注释支持。
@Async 注解方法
有时候需要任务异步执行,不然太耗时,Spring提供注解 @Async 标注异步方法执行。
代码语言:javascript复制@Service
public class SchedulerPoolService {
//此注解为异步方法注解,如果注解到类上,表示此类的所有方法都为异步方法
@Async()
@Scheduled(fixedDelay = 3000)
public void task(){
Thread thread = Thread.currentThread();
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("[" Thread.currentThread().getName() "]" "[" new Date() "]" "task-id:" thread.getId() ",group:" thread.getThreadGroup());
}
}
运行结果:
可以看出,任务每3秒执行一次,而且线程号也不一样,说明是异步执行。
@EnableAsync 注解类
还可以通过 @EnableAsync 注解服务类:
代码语言:javascript复制@Service
@EnableAsync
public class SchedulerPoolService {
@Async()
@Scheduled(fixedDelay = 3000)
public void task(){
Thread thread = Thread.currentThread();
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("[" Thread.currentThread().getName() "]" "[" new Date() "]" "task-id:" thread.getId() ",group:" thread.getThreadGroup());
}
}
调用:
代码语言:javascript复制public class TaskSchedulerTest {
public static void main(String[] args) {
ApplicationContext context = new AnnotationConfigApplicationContext(TaskSchedulerDemo.SpringConfig.class);
}
}
运行结果:
一般 @EnableScheduling 和 @EnableAsync 都会结合 @Configuration 使用,用于配置类。 @Configuration @EnableAsync @EnableScheduling public class AppConfig { }
@Async 指定执行器
由上面例子结果看出,@Async 注解默认使用任务执行器 SimpleAsyncTaskExecutor,而此实现每次执行一个提交的任务时候都会新建一个线程,没有线程的复用,一般使用ThreadPoolTaskExecutor 来代替。
当需要指定执行器时,可以使用@Async注解的 value属性。
代码语言:javascript复制@Service
public class SchedulerPoolService {
@Async("taskExecutor")
@Scheduled(fixedDelay = 3000)
public void task(){
Thread thread = Thread.currentThread();
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("[" Thread.currentThread().getName() "]" "[" new Date() "]" "task-id:" thread.getId() ",group:" thread.getThreadGroup());
}
}
applicationContext.xml中注解配置如下:
代码语言:javascript复制<bean id = "taskExecutor" class="org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor">
<property name="corePoolSize" value = "5"></property>
<property name = "maxPoolSize" value="10"></property>
<property name="queueCapacity" value="25"></property>
</bean>
也可以使用 task:executor 配置,下文会讲解。
task命名空间
从Spring 3.0开始,有一个用于配置TaskExecutor和TaskScheduler实例的XML命名空间。它还提供了一种便利的方法来配置要用触发器调度的任务。
scheduler元素
创建具有指定线程池大小的ThreadPoolTaskScheduler实例。
代码语言:javascript复制<task:scheduler id="scheduler" pool-size="10"/>
如果不提供“池大小”属性,默认线程池将只有一个线程。调度程序没有其他配置选项。
executor元素
创建一个ThreadPoolTaskExecutor实例。
代码语言:javascript复制<task:executor id="executor" pool-size="10"/>
“executor”元素比“scheduler”元素支持更多的配置选项。首先,ThreadPoolTaskExecutor的线程池本身更具可配置性,pool-size 可以使得执行程序的线程池具有不同的核心值和最大大小,而不是单一大小。
代码语言:javascript复制<task:executor id="executorWithPoolSizeRange" pool-size="5-25" queue-capacity="100"/>
id 属性值可以用作指定执行器
代码语言:javascript复制@Async("executorWithPoolSizeRange")
public void foo() {
System.out.println("foo, " Thread.currentThread().getName());
}
queue-capacity 主要思想是,当提交任务时,如果当前活跃线程的数量小于 core size,执行器将首先尝试使用空闲线程。如果已经达到 core size,那么只要队列的容量未满,任务就会被添加到队列中。 只有在达到queue-capacity时,执行器才会创建一个超出core size的新线程。如果已达到 max size,则执行程序将拒绝该任务。
默认情况下,队列是无限的,但这不是理想的配置,因为如果在所有池线程繁忙时向队列添加了足够的任务,就会导致outofmemoryerror错误。此外,如果队列是无限的,那么max size根本不起作用。因为执行器将总是在线程数超出core size时,将新建的线程加入队列。一个队列必须是有限的。
scheduled-tasks元素
可以通过 scheduled-tasks 配置要调度的任务。
代码语言:javascript复制<task:scheduled-tasks scheduler="myScheduler">
<task:scheduled ref="beanA" method="methodA" fixed-delay="5000" initial-delay="1000"/>
<task:scheduled ref="beanB" method="methodB" fixed-rate="5000"/>
<task:scheduled ref="beanC" method="methodC" cron="*/5 * * * * MON-FRI"/>
</task:scheduled-tasks>
<task:scheduler id="myScheduler" pool-size="10"/>
pojo:
代码语言:javascript复制@Service
public class beanA{
public void methodA() {
System.out.println(Thread.currentThread().getName() ":执行");
}
}
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
发布者:全栈程序员栈长,转载请注明出处:https://javaforall.cn/183254.html原文链接:https://javaforall.cn