zl程序教程

您现在的位置是:首页 >  后端

当前栏目

spring任务调度scheduled_golang 任务调度

SpringGolang 任务调度 Scheduled
2023-06-13 09:14:42 时间

任务调度接口:TaskScheduler

除了TaskExecutor抽象之外,Spring 3.0还引用了任务调度接口 TaskScheduler,它提供了多种方法来调度将来某个时间点要运行的任务。

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。

public interface Trigger { 

Date nextExecutionTime(TriggerContext triggerContext);
}

TriggerContext 保存 Trigger 接口任务执行调度的信息。它封装了所有相关的数据,如果需要,将来可以对其进行扩展。TriggerContext是一个接口(默认情况下使用SimpleTriggerContext实现)。在这里,您可以看到哪些方法可用于触发器实现。

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感受下:

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

以单个线程方式执行定时任务,适用于简单场景。

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

public class ThreadPoolTaskScheduler extends ExecutorConfigurationSupport 
implements AsyncListenableTaskExecutor, SchedulingTaskExecutor, TaskScheduler

除实现了TaskScheduler接口中的方法外,它还包含了一些对ScheduledThreadPoolExecutor进行操作的接口,大多数场景下都使用它来进行任务调度。

任务调度Demo

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:

<context:component-scan base-package="TaskSchedulerDemo" />
<bean id = "taskScheduler" class="org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler">
<property name="poolSize" value = "5"></property>
</bean>

2、定义一个任务DataSimulation:

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 对象:

@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、调用:

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中注解配置如下:

<!--添加注解的扫描包-->
<context:component-scan base-package="TaskSchedulerAnnoStartDemo" />
<!--配置注解驱动-->
<task:annotation-driven />
<task:scheduler id="myScheduler" pool-size="5"/>

<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 注解创建定时任务

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、加载配置文件即可,不需要手动启动任务。

public class testTaskExecutor { 

public static void main(String[] args) { 

ApplicationContext context = new ClassPathXmlApplicationContext(new String[] { 
"TaskSchedulerDemo/applicationContext.xml"});
}
}

运行结果:

完全注解开发(自动启动)

还可以结合配置类 @Configuration,@EnableScheduling 开启配置计划任务,实现完全注解开发,不需要手动启动任务。 配置类SpringConfig:

@Configuration
@ComponentScan("TaskSchedulerAnnoStartDemo")
@EnableScheduling // 开启配置计划任务
public class SpringConfig { 

@Bean
public TaskScheduler getTaskScheduler(){ 

ThreadPoolTaskScheduler threadPoolTaskScheduler = new ThreadPoolTaskScheduler();
threadPoolTaskScheduler.setPoolSize(5);
return threadPoolTaskScheduler;
}
}

启动类:

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默认的事件机制是同步的。举个例子:

@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 标注异步方法执行

@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 注解服务类:

@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());
}
}

调用:

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属性。

@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中注解配置如下:

<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实例。

<task:scheduler id="scheduler" pool-size="10"/>

如果不提供“池大小”属性,默认线程池将只有一个线程。调度程序没有其他配置选项。

executor元素

创建一个ThreadPoolTaskExecutor实例。

<task:executor id="executor" pool-size="10"/>

“executor”元素比“scheduler”元素支持更多的配置选项。首先,ThreadPoolTaskExecutor的线程池本身更具可配置性,pool-size 可以使得执行程序的线程池具有不同的核心值和最大大小,而不是单一大小。

<task:executor id="executorWithPoolSizeRange" pool-size="5-25" queue-capacity="100"/>

id 属性值可以用作指定执行器

@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 配置要调度的任务。

<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:

@Service
public class beanA{ 

public void methodA() { 

System.out.println(Thread.currentThread().getName() + ":执行");
}
}

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

发布者:全栈程序员栈长,转载请注明出处:https://javaforall.cn/183254.html原文链接:https://javaforall.cn