Spring Boot实现定时任务

69 篇文章 1 订阅
21 篇文章 1 订阅

在应用开发中,经常都有用到在后台跑定时任务的需求。比如需要在服务后台跑一个定时任务来进行数据清理、数据定时增量同步、定时发送邮件、爬虫定时抓取等等,这种情况下,我们往往需要执行定时任务。在java中定时任务有多种实现方式,比如使用线程、使用Timer、使用ScheduledExecutorService、Spring Task等等。本文会简单讲述一下上述几种方式的实现方法。

1. 使用普通线程Thread

创建一个thread,然后让它在while循环里一直运行着,通过sleep方法来达到定时任务的效果。这样可以快速简单的实现,代码如下:

public class Task1 {
public static void main(String[] args) {
  // run in a second
  final long timeInterval = 1000;
  Runnable runnable = new Runnable() {
  public void run() {
    while (true) {
      // ------- code for task to run
      System.out.println("Hello !!");
      // ------- ends here
      try {
       Thread.sleep(timeInterval);
      } catch (InterruptedException e) {
        e.printStackTrace();
      }
      }
    }
  };
  Thread thread = new Thread(runnable);
  thread.start();
  }
}

可以实现间隔一定时间执行任务的需求,但是功能比较单一,不能定时。这种方式比较简单明了,示例代码中就单独展示了。

2. Timer方式

Timer是JDK自带的java.util.Timer包中的工具类,通过调度java.util.TimerTask的方式让程序再某个时间按照某一个频度执行,一般用的较少。Timer调度任务的方法有两种,一个事schedule方法,另一个是scheduleAtFixedRate方法。先看一下Timer类中两个方法的重载方法:

S.N.方法及说明
1.void schedule(TimerTask task, long delay)
将task延迟delay毫秒后执行一次
2.void schedule(TimerTask task, Date time)
将task在time启动执行一次
3.void schedule(TimerTask task, long delay, long period)
将task延迟delay毫秒后启动,之后每隔period毫秒执行一次
4.void schedule(TimerTask task, Date firstTime, long period)
将task在firstTime启动,之后每隔period毫秒执行一次,如果firstTime小于当前时间,则立即执行任务,如果某次任务执行时间大于period,本次任务执行结束,立即执行下一次任务,之后每个任务的周期还是period,整个过程不存在追任务的行为。
5.void scheduleAtFixedRate(TimerTask task, long delay, long period)
将task延迟delay毫秒后启动,之后每隔period毫秒执行一次,使用方法和3一致
6.void scheduleAtFixedRate(TimerTask task, Date firstTime, long period)
将task在firstTime启动,之后每隔period毫秒执行一次,与方法4不同的是,如果firstTime小于当前时间,则立即执行任务,之后会存在追任务的过程,比如period为5s,基本上每个任务执行时间是3s,那剩下来的两秒不再等待,直接用来执行任务,知道某个时间点,达到预期任务执行的次数。

2.1 schedule task demo

@Component
public class ScheduleTask {
    @PostConstruct
    public void startScheduleTask(){
        Timer timer = new Timer();
        //耗时2s左右的任务
        TimerTask task1 = new TimerTask() {
            @Override
            public void run() {
                //获得该任务该次执行的期望开始时间
                System.out.println("expect start time: " + new Date(scheduledExecutionTime()));
                System.out.println("task1 start: " + new Date());
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                }
                System.out.println("task1 end: " + new Date());
            }
        };
        LocalDateTime localDateTime1 = LocalDateTime.now().plusSeconds(-11);
        Date date = Date.from(localDateTime1.atZone(ZoneId.systemDefault()).toInstant());

        System.out.println("now: " + new Date());
        System.out.println("timer start: " + date);
        //设置调度的启动时间为11s前
        timer.schedule(task1, date, 5000);
    }
}

设置任务启动时间为当前时间前的11S,每个任务执行时间为2S。

2.2 schedule task执行过程

now: Wed Sep 05 20:54:10 CST 2018
#设置任务启动时间为11S前
timer start: Wed Sep 05 20:53:59 CST 2018
#首次任务期望执行时间为当前,并不是11S前
expect start time: Wed Sep 05 20:54:10 CST 2018
task1 start: Wed Sep 05 20:54:10 CST 2018
task1 end: Wed Sep 05 20:54:12 CST 2018
#每次任务执行间隔为5S
expect start time: Wed Sep 05 20:54:15 CST 2018
task1 start: Wed Sep 05 20:54:15 CST 2018
task1 end: Wed Sep 05 20:54:17 CST 2018
expect start time: Wed Sep 05 20:54:20 CST 2018
task1 start: Wed Sep 05 20:54:20 CST 2018
task1 end: Wed Sep 05 20:54:22 CST 2018

2.3 scheduleAtFixedRate task demo

@Component
public class scheduleAtFixedRateTask {
    @PostConstruct
    public void startScheduleAtFixedRateTask() {
        Timer timer = new Timer();
        //耗时2s左右的任务
        TimerTask task1 = new TimerTask() {
            @Override
            public void run() {
                //获得该任务该次执行的期望开始时间
                System.out.println("expect run time: " + new Date(scheduledExecutionTime()));
                System.out.println("task1 start: " + new Date());
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                }
                System.out.println("task1 end: " + new Date());
            }
        };
        LocalDateTime localDateTime1 = LocalDateTime.now().plusSeconds(-11);
        Date date = Date.from(localDateTime1.atZone(ZoneId.systemDefault()).toInstant());

        System.out.println("now: " + new Date());
        System.out.println("timer start: " + date);
        //设置调度的启动时间为11s前
        timer.scheduleAtFixedRate(task1, date, 5000);
    }
}

设置任务启动时间为当前时间前的11S,每个任务执行时间为2S。

2.4 scheduleAtFixedRate task执行过程

now: Wed Sep 05 20:59:24 CST 2018
#设置任务启动时间为11S前
timer start: Wed Sep 05 20:59:13 CST 2018
#首次任务期望启动时间为11S前
expect run time: Wed Sep 05 20:59:13 CST 2018
task1 start: Wed Sep 05 20:59:24 CST 2018
task1 end: Wed Sep 05 20:59:26 CST 2018
expect run time: Wed Sep 05 20:59:18 CST 2018
#上次任务执行结束并没有接着等3秒,直接进行下一次任务
task1 start: Wed Sep 05 20:59:26 CST 2018
task1 end: Wed Sep 05 20:59:28 CST 2018
expect run time: Wed Sep 05 20:59:23 CST 2018
task1 start: Wed Sep 05 20:59:28 CST 2018
task1 end: Wed Sep 05 20:59:30 CST 2018
expect run time: Wed Sep 05 20:59:28 CST 2018
task1 start: Wed Sep 05 20:59:30 CST 2018
task1 end: Wed Sep 05 20:59:32 CST 2018
#追赶上
expect run time: Wed Sep 05 20:59:33 CST 2018
task1 start: Wed Sep 05 20:59:33 CST 2018
task1 end: Wed Sep 05 20:59:35 CST 2018
expect run time: Wed Sep 05 20:59:38 CST 2018
task1 start: Wed Sep 05 20:59:38 CST 2018
task1 end: Wed Sep 05 20:59:40 CST 2018

注意:在示例代码中,为task类添加了@Component注解,在spring启动时,会进行Bean注册,执行@PostConstruct方法,实现了任务排期。为了展现效果,可以在测试相应task的时候,把其他的task的@Component注解注释掉。

3. ScheduledThreadPoolExecutor

Timer存在一些缺陷,比如Timer运行多个TimeTask时,只要其中有一个因任务报错没有捕获抛出的异常,其它任务便会自动终止运行。jdk5之后出现了一种新的定时器工具ScheduledThreadPoolExecutor,ScheduledThreadPoolExecutor继承自ThreadPoolExecutor,但是ScheduledThreadPoolExecutor几个构造方法中只能设置corePoolSize。它作为一个使用corePoolSize线程和一个无界队列的固定大小的线程池,调整maximumPoolSize没有效果。一个线程负责一个schedulexxx方法的执行,corePoolSize小于执行的schedulexxx方法个数时,放入任务队列。

S.N.方法及说明
1.ScheduledFuture<?> schedule(Runnable command, long delay, TimeUnit unit)
延迟delay个unit单位执行一次command,并返回一个future,该future的get只会是null
2.<V> ScheduledFuture<V> schedule(Callable<V> callable, long delay, TimeUnit unit)
延迟delay个unit单位执行一次callable,并返回一个future,future可以拿到callable的结果
3.ScheduledFuture<?> scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit)
延迟delay个unit单位启动command,之后每period个unit单位执行一次command
4.ScheduledFuture<?> scheduleWithFixedDelay(Runnable command, long initialDelay, long delay, TimeUnit unit)
延迟delay个unit单位启动command,每个任务之间的间隔为delay个unit单位

3.1 ScheduleWithFixedDelayTask demo

@Component
public class ScheduleWithFixedDelayTask {

    @PostConstruct
    public void starkTask(){
        TimerTask task1 = new TimerTask() {
            @Override
            public void run() {
                //获得该任务该次执行的期望开始时间
                System.out.println("expect start time: " + new Date(scheduledExecutionTime()));
                System.out.println("task1 start: " + new Date());
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                }
                System.out.println("task1 end: " + new Date());
            }
        };

        System.out.println("now: " + new Date());
        ScheduledThreadPoolExecutor executor = new ScheduledThreadPoolExecutor(2);
        executor.scheduleWithFixedDelay(task1, 0, 1000, TimeUnit.MILLISECONDS);
    }
}

3.2 ScheduleWithFixedDelayTask执行过程

now: Wed Sep 05 21:51:36 CST 2018
expect start time: Thu Jan 01 08:00:00 CST 1970
task1 start: Wed Sep 05 21:51:36 CST 2018
task1 end: Wed Sep 05 21:51:38 CST 2018
expect start time: Thu Jan 01 08:00:00 CST 1970
#下一次任务开始时间间隔了1S
task1 start: Wed Sep 05 21:51:39 CST 2018
task1 end: Wed Sep 05 21:51:41 CST 2018
expect start time: Thu Jan 01 08:00:00 CST 1970
task1 start: Wed Sep 05 21:51:42 CST 2018
task1 end: Wed Sep 05 21:51:44 CST 2018
expect start time: Thu Jan 01 08:00:00 CST 1970
task1 start: Wed Sep 05 21:51:45 CST 2018
task1 end: Wed Sep 05 21:51:47 CST 2018

另外可以发现,在ScheduleWithFixedDelayTask中,期望开始时间是不生效的。

3.3 ScheduleAtFixedRateTask demo

@Component
public class ScheduleAtFixedRateTask {
    @PostConstruct
    public void starkTask(){
        TimerTask task2 = new TimerTask() {
            @Override
            public void run() {
                //获得该任务该次执行的期望开始时间
                System.out.println("expect start time: " + new Date(scheduledExecutionTime()));
                System.out.println("task2 start: " + new Date());
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                }
                System.out.println("task2 end: " + new Date());
            }
        };

        System.out.println("now: " + new Date());
        ScheduledThreadPoolExecutor executor = new ScheduledThreadPoolExecutor(2);
        executor.scheduleAtFixedRate(task2, 0, 1000, TimeUnit.MILLISECONDS);
    }
}

3.2 ScheduleAtFixedRateTask执行过程

now: Wed Sep 05 21:54:37 CST 2018
expect start time: Thu Jan 01 08:00:00 CST 1970
task2 start: Wed Sep 05 21:54:37 CST 2018
task2 end: Wed Sep 05 21:54:39 CST 2018
expect start time: Thu Jan 01 08:00:00 CST 1970
task2 start: Wed Sep 05 21:54:39 CST 2018
task2 end: Wed Sep 05 21:54:41 CST 2018
expect start time: Thu Jan 01 08:00:00 CST 1970
task2 start: Wed Sep 05 21:54:41 CST 2018
task2 end: Wed Sep 05 21:54:43 CST 2018
expect start time: Thu Jan 01 08:00:00 CST 1970
task2 start: Wed Sep 05 21:54:43 CST 2018
task2 end: Wed Sep 05 21:54:45 CST 2018

4. Spring Task

4.1 任务定义

@Component
@Slf4j
public class SpringTask {
    @Async
    @Scheduled(cron = "0/1 * * * * *")
    public void scheduled1() throws InterruptedException {
        log.info("scheduled1 每1秒执行一次:{}", LocalDateTime.now());
        Thread.sleep(3000);
    }

    @Scheduled(fixedRate = 1000)
    public void scheduled2() throws InterruptedException {
        log.info("scheduled2 每1秒执行一次:{}", LocalDateTime.now());
        Thread.sleep(3000);
    }

    @Scheduled(fixedDelay = 3000)
    public void scheduled3() throws InterruptedException {
        log.info("scheduled3 上次执行完毕后隔3秒继续执行:{}", LocalDateTime.now());
        Thread.sleep(5000);
    }
}

这里讲解一下各个注解的含义:

@Scheduled表名方法为定时任务

  • cron:cron表达式,根据表达式循环执行,@Scheduled(cron = “0/5 * * * * *”)表示任务将在5、10、15、20…这种情况下进行工作,cron表达式可在线生成
  • fixedRate:每隔多久执行一次,@Scheduled(fixedRate = 1000) 假设第一次工作时间为2018-09-06 10:30:01,工作时长为3秒,那么下次任务的时候就是2018-09-06 10:30:04,配置成异步后,只要到了执行时间就会开辟新的线程工作;如果@Scheduled(fixedRate = 3000) 假设第一次工作时间为2018-09-06 10:30:01,工作时长为1秒,那么下次任务的时间依然是2018-09-06 10:30:04
  • fixedDelay:当前任务执行完毕后等待多久继续下次任务,@Scheduled(fixedDelay = 3000) 假设第一次任务工作时间为2018-09-06 10:30:01,工作时长为5秒,那么下次任务的时间就是2018-09-06 10:30:09
  • initialDelay:第一次执行延迟时间,只是做延迟的设定

@Async代表该任务可以进行异步工作,由原本的串行改为并行

4.2 开启线程池

@SpringBootApplication
@EnableScheduling
@EnableAsync
public class SpringTaskApplicationContext {
    public static void main(String[] args) {
        SpringApplication.run(SpringTaskApplicationContext.class, args);
    }

    /**
     *默认情况下 TaskScheduler 的 poolSize = 1 多个任务的情况下,如果第一个任务没执行完毕,后续的任务将会进入等待状态
     * @return 线程池
     */
    @Bean
    public TaskScheduler taskScheduler() {
        ThreadPoolTaskScheduler taskScheduler = new ThreadPoolTaskScheduler();
        taskScheduler.setPoolSize(10);
        return taskScheduler;
    }
}
  • @EnableScheduling: 表示开启对@Scheduled注解的解析
  • @EnableAsync: 注解表示开启@Async注解的解析,将串行化的任务给并行化。比如@Scheduled(cron = “0/1 * * * * *”)第一次工作时间为2018-09-06 10:30:01,工作周期为3秒;如果不加@Async那么下一次工作时间就是2018-09-06 10:30:04。如果加了@Async下一次工作时间就是2018-09-06 10:30:02

4.3 执行结果

scheduled2 每1秒执行一次:2018-09-06T10:23:54.510
scheduled3 上次执行完毕后隔3秒继续执行:2018-09-06T10:23:54.510
#scheduled1开启异步,每秒执行一次
scheduled1 每1秒执行一次:2018-09-06T10:23:55.014
scheduled1 每1秒执行一次:2018-09-06T10:23:56.002
scheduled1 每1秒执行一次:2018-09-06T10:23:57.001
#Schedule2定义每秒执行一次,但是任务执行时间为3S,没开启异步,所以每隔3S执行一次
scheduled2 每1秒执行一次:2018-09-06T10:23:57.513
scheduled1 每1秒执行一次:2018-09-06T10:23:58.002
scheduled1 每1秒执行一次:2018-09-06T10:23:59.002
scheduled1 每1秒执行一次:2018-09-06T10:24:00.001
scheduled2 每1秒执行一次:2018-09-06T10:24:00.513
scheduled1 每1秒执行一次:2018-09-06T10:24:01
scheduled1 每1秒执行一次:2018-09-06T10:24:02.002
#scheduled3 任务执行时间5S,任务之间间隔3S,总共8S
scheduled3 上次执行完毕后隔3秒继续执行:2018-09-06T10:24:02.513
scheduled1 每1秒执行一次:2018-09-06T10:24:03.001
scheduled2 每1秒执行一次:2018-09-06T10:24:03.513
…………

示例代码:码云 – 卓立 – 定时任务

参考链接:

  1. Java实现定时任务的三种方法
  2. java定时工具的辟谣
  3. 详解java定时任务
  • 2
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值