一、定时任务定义:

定时任务简单地说,就是在指定的时间,按照指定的频率来执行某一个方法。
现在的应用程序,早已不是只由增、删、改、查组成的应用程序了,高复杂度,高并发早已是标配,而任务的定时调度与执行也是对程序的基本要求了。
例如:运营商会在月末清空未使用完的流量,备忘录提醒、闹钟、基金定投等功能,都是定时器的应用场景。

二、实现定时任务的四种方式

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