Spring-boot定时任务

create project

使用IDEA创建一个scheduler-demo项目,这里可以使用spring initializr插件初始化spring boot

@EnableScheduling

注解EnableScheduling表示允许Spring Boot开启定时任务管理

@SpringBootApplication
@EnableScheduling
public class SchedulerDemoApplication {

    public static void main(String[] args) {
        SpringApplication.run(SchedulerDemoApplication.class, args);
    }
}

@Scheduled

@Scheduled用来生成一个定时任务

@Scheduled的配置如下

param explain
fixedDelay 两个定时任务之间的间隔,即前一个任务执行完后,延迟delay毫秒再执行下一个任务
fixedDelayString 与fixedDelay相同,只不过用string类型表示时间
fixedRate 固定周期执行任务,例如每隔10s执行一次任务
fixedRateString 与fixedRate相同,用string表示时间
cron 用cron表达式来管理定时任务
initialDelay 执行第一个定时任务的延迟时间,只对fixedDelay或fixedRate任务生效
initialDelayString 与initialDelay相同
zone 时区,默认使用server的时区

fixedDelay与fixedRate的区别

对于fixedDelay任务,后个任务一定需要在前一个任务执行完并且再过delay毫秒后才会执行。对于下面的例子来说,delay为5s,而任务执行需要5s,所有每隔10s任务才会执行一次

@Scheduled(fixedDelay = 5000)
public void fixedDelayTask() throws Exception{
    SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm");
    System.out.println(dateFormat.format(new Date()) + ":" + " task of fixed delay");
    Thread.sleep(5000);
}

对于fixedRate任务,他们的执行是严格按照时间周期,即使上一个任务还未执行完毕,只要时间一到就会里面执行

@Scheduled(fixedRate = 5000,initialDelay = 5000)
public void fixedRateTask() throws Exception{
    SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    System.out.println(dateFormat.format(new Date()) + ":" + " task of fixed rate");
    Thread.sleep(5000);
}

cron表达式

@Scheduled(cron = "[Seconds] [Minutes] [Hours] [Day of month] [Month] [Day of week]")
  • second 取值在[0-59],或者特殊字符 , - * /
  • minute 取值在[0-59], 或者特殊字符, - * /
  • hour 取值在[0-23],或者特殊字符, - * /
  • day of month 每个月第几天,取值在[0-31],或者特殊字段, - * / ?
  • month 取值[1-12]或者月份的英文描述,特殊字符为, - * /
  • day of week 取值为[1-7],1表示星期天,2表示星期一,以此类推,也可用英文描述的星期,例如SUN,特殊字符为, - * / #
特殊字符 含义
* 表示任意值,若second为*,表示每秒都会触发该事件
, 表示列出枚举值,若minute为5,20 则表示第5分钟和第20分钟会触发事件
/ 表示起始时间开始触发,然后每隔固定周期都会触发,例如minute为5/20,则在5分,25分,45分都会触发事件
- 表示取值范围,若minute为5-20,则表示从5分到20分,每分钟都会触发事件
? 只能用在每月第几天和星期两个域。表示不指定值,当2个子表达式其中之一被指定了值以后,为了避免冲突,需要将另一个子表达式的值设为”?”
L 表示最后,只能出现在week或者day of month中,若week为1L,表示在最后一个星期天
W 表示有效工作日(周一到周五),只能出现在day of month中
# 用于确定每个月第几个星期几,只能出现在day of month中,例如1#3 表示每个月第三个星期日

cron示例

每天10点

0 0 10 * * *

每隔5分钟

* */5 * * * *

每天8点,8点半,9点,9点半,10点

* */30 8-10 * * *

自定义线程池来管理定时任务

自定义线程池配置,例如线程池size,线程池名称

@Configuration
public class ScheduleConfig implements SchedulingConfigurer {
    @Override
    public void configureTasks(ScheduledTaskRegistrar scheduledTaskRegistrar) {
        ThreadPoolTaskScheduler taskScheduler = new ThreadPoolTaskScheduler();
        taskScheduler.setPoolSize(10);
        taskScheduler.setThreadNamePrefix("schedule-task-pool");
        taskScheduler.initialize();

        scheduledTaskRegistrar.setTaskScheduler(taskScheduler);
    }
}

定义一个cron任务

@Scheduled(cron = "*/5 * * * * *")
public void cronTask(){
    String name = Thread.currentThread().getName();
    SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    System.out.println(String.format("%s: current thread %s", dateFormat.format(new Date()), name));
}

运行程序,可以得到如下的运行结果,可以看到此时定时任务是运行在自定义的线程池里

动态管理定时任务

使用ThreadPoolTaskScheduler来生成定时任务,在通过维护定时任务的ScheduleFuture的map来实现动态管理定时任务

DynamicScheduleTaskManager

@Component
public class DynamicScheduleTaskManager {
    private ThreadPoolTaskScheduler taskScheduler = new ThreadPoolTaskScheduler();

    private Map<Integer, ScheduledFuture> futureMap = new HashMap<>();

    @PostConstruct
    public void init(){
        taskScheduler.initialize();
    }

    /**
     * 增加定时任务,若任务已存在,则删除旧任务,更新新任务
     * @param taskConfig
     */
    public void addOrUpdateTask(TaskConfig taskConfig){
        int taskConfigId = taskConfig.getId();
        if (futureMap.containsKey(taskConfigId)){
            deleteTask(taskConfig);
        }
        ScheduledFuture<?> scheduledFuture = taskScheduler.schedule(() -> {
            SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
            String timeFormat = dateFormat.format(new Date());
            System.out.println(String.format("time:%s,process task,id:%d",timeFormat,taskConfigId));
        }, new CronTrigger(taskConfig.getCron()));
        futureMap.put(taskConfigId, scheduledFuture);
    }

    /**
     * 删除任务
     * @param taskConfig
     */
    public void deleteTask(TaskConfig taskConfig){
        int taskConfigId = taskConfig.getId();
        if (futureMap.containsKey(taskConfigId)){
            ScheduledFuture scheduledFuture = futureMap.get(taskConfigId);
            scheduledFuture.cancel(true);
            System.out.println("delete task,id:" + taskConfigId);
        }
    }
}        

TaskConfig

@Getter
@Setter
public class TaskConfig {
    /**
     * id of task
     */
    private int id;

    /**
     * cron expression
     */
    private String cron;

}

ScheduleController

@RestController
@RequestMapping(produces = "application/json; charset=utf-8")
public class ScheduleController {

    @Autowired
    private DynamicScheduleTaskManager scheduleTaskManager;

    @PostMapping("/add/task")
    public String addTask(@RequestBody TaskConfig taskConfig){
        scheduleTaskManager.addOrUpdateTask(taskConfig);
        return "success";
    }

    @PostMapping("/delete/task")
    public void deleteTask(@RequestBody TaskConfig taskConfig){
        scheduleTaskManager.deleteTask(taskConfig);
    }
}    

测试

####1. 新增taskConfig

####2. 更新taskConfig

####3. 删除taskConfig

####4.输出

参考文献


Reprint please specify: wbl Spring-boot定时任务

Previous
Spring-Boot Quick start Spring-Boot Quick start
Create Project构建一个Spring Boot的项目非常方面,可以使用官方提供的Spring Initializr,打开https://start.spring.io/,输入项目基本信息即可构建一个项目 另外你还可以设置需要引
2019-04-21
Next
Raft协议之日志复制 Raft协议之日志复制
上篇Raft协议之Leader选举介绍了Leader选举的过程。Leader会处理来自客户端的请求,并将客户端更新操作以消息(Append Entries消息)的形式发送到集群中所有Follower节点。本文将介绍Raft协议日志复制的流程
2019-03-31