一、定时任务定义:
定时任务简单地说,就是在指定的时间,按照指定的频率来执行某一个方法。
现在的应用程序,早已不是只由增、删、改、查组成的应用程序了,高复杂度,高并发早已是标配,而任务的定时调度与执行也是对程序的基本要求了。
例如:运营商会在月末清空未使用完的流量,备忘录提醒、闹钟、基金定投等功能,都是定时器的应用场景。
二、实现定时任务的四种方式
2.1 第一种方式: 使用java的Timer
new Timer("testTimer").schedule(new TimerTask() {
@Override
public void run() {
to do something...
}
}, 1000,2000);
1000ms是延迟启动时间,2000ms是定时任务周期,每2s执行一次
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
try {
Date date = dateFormat.parse("2020-07-09 12:00:00.000");
new Timer("testTimer").scheduleAtFixedRate(new TimerTask() {
@Override
public void run() {
to do something...
}
}, date,2000);
} catch (ParseException e) {
e.printStackTrace();
}
date是开始时间,2000ms是定时任务周期,每2s执行一次
timer有2中方法schedule和scheduleAtFixedRate,前者会等任务结束在开始计算时间间隔,后者是在任务开始就计算时间,有并发的情况
2.2 第二种方式:使用java.util.concurrent的ScheduledExecutorService
scheduledExecutorService.schedule(new Runnable() {
@Override
public void run() {
to do somthing....
}
},1, TimeUnit.SECONDS);
延迟1s启动,执行一次
ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(10);
scheduledExecutorService.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
to do something...
}
}, 1, 1, TimeUnit.SECONDS);
延迟1s启动,每隔1s执行一次,是前一个任务开始时就开始计算时间间隔,但是会等上一个任务结束在开始下一个
ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(10);
scheduledExecutorService.scheduleWithFixedDelay(new Runnable() {
@Override
public void run() {
to do something...
}
}, 1, 1, TimeUnit.SECONDS);
延迟1s启动,在前一个任务执行完成之后,延迟1s在执行
2.3 第三种方式:使用Spring的@Scheduled 注解
传统方式
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
@Service
public class SpringTask {
private static final Logger log = LoggerFactory.getLogger(SpringTask.class);
@Scheduled(cron = "1/5 * * * * *")
public void task1(){
to do something...
}
@Scheduled(initialDelay = 1000,fixedRate = 1*1000)
public void task2(){
to do something...
}
}
灵活方式
1.定义CronTaskDef.java实体
public class CronTaskDef {
private String cronId;
private String cronClassName;
private String cronExpression;
private String cronStatus;
private String cronPara;
public CronTaskDef(String cronId, String cronClassName, String cronExpression, String cronPara,String cronStatus) {
this.cronId = cronId;
this.cronClassName = cronClassName;
this.cronExpression = cronExpression;
this.cronStatus = cronStatus;
this.cronPara = cronPara;
}
}
2.定义bat.yaml配置
# 定时任务
- cronId: 9
cronClassName: com.xxx.xxx.schedule.TaskARQCron
cronExpression: 0 0 0/2 * * ?
cronPara:
cronStatus: 1
3.扫描并统一启动定时任务ScheduleConfig.java
@Autowired
private ThreadPoolTaskScheduler threadPoolTaskScheduler;
ScheduledFuture<?> future = threadPoolTaskScheduler.schedule((Runnable) Class.forName(cron.getCronClassName()).newInstance(), new CronTrigger(cron.getCronExpression()));
public void reStartCrons(List<CronTaskDef> crons) {
try {
Map<String, CronTaskDef> executeTask = new HashMap<>();
//遍历所有库中动态数据,根据库中class取出所属的定时任务对象进行关闭,每次都会把之前所有的定时任务都关闭,根据新的状态重新启用一次,达到最新配置
for (CronTaskDef cron : crons) {
ScheduledFuture<?> scheduledFuture = scheduleMap.get(cron.getCronId());
//一定判空否则出现空指针异常,ToolUtil为自己写的工具类此处只需要判断对象是否为空即可
if (scheduledFuture != null) {
try{
logger.info("定时任务{},isDone:{}",cron.toString(),scheduledFuture.isDone());
boolean flag = scheduledFuture.cancel(false);
if(flag){//等待任务执行完,再取消
scheduledFuture.cancel(true);
}else{
logger.info("定时任务:{},正在执行中--------------------",cron.toString());
executeTask.put(cron.getCronId(),cron);//将未处理完的cron重新放到HashMap中
}
}catch (Exception e){
logger.error("scheduledFuture.cancel failed");
}
}
}
//scheduleMap.clear();
for (CronTaskDef cron : crons) {
//判断当前定时任务是否有效,COMMON_API_WRAPPER_STATIC_VALUE.VALIDFLAG.TRUE为有效标识
//并且不是正在执行的cron
if (CronStatus.ON.getStatus().equals(cron.getCronStatus()) && (!executeTask.containsKey(cron.getCronId()))) {
//开启一个新的任务,库中存储的是全类名(包名加类名)通过反射成java类,读取新的时间
ScheduledFuture<?> future = threadPoolTaskScheduler.schedule((Runnable) Class.forName(cron.getCronClassName()).newInstance(), new CronTrigger(cron.getCronExpression()));
//这一步非常重要,之前直接停用,只停用掉了最后启动的定时任务,前边启用的都没办法停止,所以把每次的对象存到map中可以根据key停用自己想要停用的
logger.info("重启定时任务{}",cron.toString());
scheduleMap.put(cron.getCronId(), future);
}
}
if(executeTask.size() == 0){
logger.info("已重启所有定时任务");
}else{
new Thread(new Worker(executeTask)).start();
}
} catch (Exception e) {
e.printStackTrace();
}
}
task1是每隔5s执行一次,{秒} {分} {时} {日期} {月} {星期}
task2是延迟1s,每隔1S执行一次
关于cron表达式,可以查阅下面
https://www.bejson.com/othertools/cron/
2.4 第四种方式:使用Quartz框架
2.4.1、加依赖
<!-- quartz -->
<dependency>
<groupId>org.quartz-scheduler</groupId>
<artifactId>quartz</artifactId>
<version>2.3.0</version>
</dependency>
<!--调度器核心包-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
<version>4.3.4.RELEASE</version>
</dependency>
2.4.2、Job实现
import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import java.text.SimpleDateFormat;
import java.util.Date;
public class HelloWorldJob implements Job {
@Override
public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
String strTime = new SimpleDateFormat("HH-mm-ss").format(new Date());
to do something...
}
}
2.4.3、调度器(可以用listener在项目启动时执行)
import org.quartz.*;
import org.quartz.impl.StdSchedulerFactory;
public class MyScheduler {
public static void main(String[] args) throws SchedulerException {
//创建调度器Schedule
SchedulerFactory schedulerFactory = new StdSchedulerFactory();
Scheduler scheduler = schedulerFactory.getScheduler();
//创建JobDetail实例,并与HelloWordlJob类绑定
JobDetail jobDetail = JobBuilder.newJob(HelloWorldJob.class).withIdentity("job1", "jobGroup1")
.build();
//创建触发器Trigger实例(立即执行,每隔1S执行一次)
Trigger trigger = TriggerBuilder.newTrigger()
.withIdentity("trigger1", "triggerGroup1")
.startNow()
.withSchedule(SimpleScheduleBuilder.simpleSchedule().withIntervalInSeconds(1).repeatForever())
.build();
//开始执行
scheduler.scheduleJob(jobDetail, trigger);
scheduler.start();
}
}
上面用的是简单触发器,也可以用Con触发器,如下
Trigger cronTrigger = TriggerBuilder.newTrigger()
.withIdentity("trigger2", "triggerGroup2")
.startNow()
.withSchedule(cronSchedule("0 42 10 * * ?"))
.build();
2.4.4、整合spring
也可以直接把上面的调度器写成配置文件,整合spring
(1)job
import java.text.SimpleDateFormat;
import java.util.Date;
public class QuarFirstJob {
public void first() {
String strTime = new SimpleDateFormat("HH-mm-ss").format(new Date());
to do something
}
}
(2)配置文件
<bean id="QuarFirstJob" class="com.zb.quartz.QuarFirstJob" />
<bean id="jobDetail"
class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean">
<property name="group" value="quartzGroup1" />
<property name="name" value="quartzJob1" />
<!--false表示等上一个任务执行完后再开启新的任务 -->
<property name="concurrent" value="false" />
<property name="targetObject">
<ref bean="QuarFirstJob" />
</property>
<property name="targetMethod">
<value>first</value>
</property>
</bean>
<!-- 调度触发器 -->
<bean id="myTrigger"
class="org.springframework.scheduling.quartz.CronTriggerFactoryBean">
<property name="name" value="trigger1" />
<property name="group" value="group1" />
<property name="jobDetail">
<ref bean="jobDetail" />
</property>
<property name="cronExpression">
<value>0/5 * * * * ?</value>
</property>
</bean>
<!-- 调度工厂 -->
<bean id="scheduler"
class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
<property name="triggers">
<list>
<ref bean="myTrigger" />
</list>
</property>
</bean>
2.4.5、时间
public class QuarFirstJob {
public void first() {
try {
TimeUnit.SECONDS.sleep(6);
} catch (InterruptedException e) {
e.printStackTrace();
}
String strTime = new SimpleDateFormat("HH-mm-ss").format(new Date());
System.out.println( strTime + ":Hello World!");
}
}
上面的配置里面写是5s间隔,把上面的sleep时间分别改成4和6,发现两次任务间隔是执行时间和间隔时间的最大值,分别是5,6