第十二节 SpringBoot的定时任务

亮子 2021-05-11 13:46:42 21816 0 0 0

1、常用定时器技术

  • Timer:这是java自带的java.util.Timer类,这个类允许你调度一个java.util.TimerTask任务。使用这种方式可以让你的程序按照某一个频度执行,但不能在指定时间运行。一般用的较少。

  • ScheduledExecutorService:也jdk自带的一个类;是基于线程池设计的定时任务类,每个调度任务都会分配到线程池中的一个线程去执行,也就是说,任务是并发执行,互不影响。

  • Spring Task:Spring3.0以后自带的task,可以将它看成一个轻量级的Quartz,而且使用起来比Quartz简单许多。

  • Quartz:这是一个功能比较强大的的调度器,可以让你的程序在指定时间执行,也可以按照某一个频度执行,配置起来稍显复杂。

2、SpringBoot定时任务实现

1)、创建任务类

@Component
@EnableScheduling
public class TaskWork {

}

2)多种定时机制

  • fixedRate:定义一个按一定频率执行的定时任务
  • fixedDelay:定义一个按一定频率执行的定时任务,与上面不同的是,改属性可以配合initialDelay, 定义该任务延迟执行时间。
  • cron:通过表达式来配置任务执行时间

cron表达式:
一个cron表达式有至少6个(也可能7个)有空格分隔的时间元素。按顺序依次为:

  • 秒(0~59)
  • 分钟(0~59)
  • 3 小时(0~23)
  • 4 天(0~31)
  • 5 月(0~11)
  • 6 星期(1~7 1=SUN 或 SUN,MON,TUE,WED,THU,FRI,SAT)
  • 年份(1970-2099)

其中每个元素可以是一个值(如6),一个连续区间(9-12),一个间隔时间(8-18/4)(/表示每隔4小时),一个列表(1,3,5),通配符。由于”月份中的日期”和”星期中的日期”这两个元素互斥的,必须要对其中一个设置。

==秒 分钟 小时 天 月 星期 年份==

每隔5秒执行一次:*/5 ** ?

每隔1分钟执行一次:0 */1 *?

0 0 10,14,16 ? 每天上午10点,下午2点,4点

0 0/30 9-17 ? 朝九晚五工作时间内每半小时

0 0 12 ? * WED 表示每个星期三中午12点

“0 0 12 ?” 每天中午12点触发

“0 15 10 ? “ 每天上午10:15触发

“0 15 10 ?” 每天上午10:15触发

“0 15 10 ? *” 每天上午10:15触发

“0 15 10 ? 2005” 2005年的每天上午10:15触发

“0 *14 ** ?” 在每天下午2点到下午2:59期间的每1分钟触发

“0 0/5 14 ?” 在每天下午2点到下午2:55期间的每5分钟触发

“0 0/5 14,18 ?” 在每天下午2点到2:55期间和下午6点到6:55期间的每5分钟触发

“0 0-5 14 ?” 在每天下午2点到下午2:05期间的每1分钟触发

“0 10,44 14 ? 3 WED” 每年三月的星期三的下午2:10和2:44触发

“0 15 10 ? * MON-FRI” 周一至周五的上午10:15触发

“0 15 10 15 * ?” 每月15日上午10:15触发

“0 15 10 L * ?” 每月最后一日的上午10:15触发

“0 15 10 ? * 6L” 每月的最后一个星期五上午10:15触发

“0 15 10 ? * 6L 2002-2005” 2002年至2005年的每月的最后一个星期五上午10:15触发

“0 15 10 ? * 6#3” 每月的第三个星期五上午10:15触发

3)、实现定时任务

@Component
@EnableScheduling
public class TaskWork {

    private int workCount = 0;

    /**
     * fixedRate 的单位是毫秒
     */

    @Scheduled(fixedRate = 1000)
    public void taskCounter() {
        workCount ++;
        System.out.println("workCount=" + workCount);
    }

    @Scheduled(cron = "0/5 * * * * *")
    public void taskRun5() throws InterruptedException {
        Date dNow = new Date( );
        SimpleDateFormat ft = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");

        System.out.println(Thread.currentThread().getName()+"=>使用cron:"+ft.format(dNow));
    }
}

4)、定时任务总结

  • 步骤

1:在启动类上写@EnableScheduling注解
2:在要定时任务的类上写@component
3:在要定时执行的方法上写@Scheduled(fixedRate=毫秒数)。

  • 示例
import java.util.Date;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

@Component
@EnableScheduling //开启定时任务
public class Jobs {

    //表示方法执行完成后5秒
    @Scheduled(fixedDelay=5000)
    public void fixedDelayJob() throws InterruptedException{  
        System.out.println("fixedDelay 每隔5秒"+new Date());
    }

    //表示每隔3秒
    @Scheduled(fixedRate=3000)
    public void fixedRateJob(){
        System.out.println("fixedRate 每隔3秒"+new Date());
    }

    //表示每天8时30分0秒执行
    @Scheduled(cron="0 0,30 0,8 ? * ? ")
    public void cronJob() {
        System.out.println(new Date()+" ...>>cron...."); 
    }
}

3、异步定时任务

1)、为啥要有异步任务

我们在使用定时任务的时候,有一种情况。比如说我们1秒钟执行一次,但是执行一次任务会花费10秒钟,那一分钟会执行60次呢?还是6次呢?
初学者对这个问题,可能不太好回答,那就通过编写代码测试一下。

package com.shenmazong.demonew.task;


import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

import java.util.Date;

@Component
@EnableScheduling
@Slf4j
public class DemoTask {

    //表示每隔1秒
    @Scheduled(fixedRate=1000)
    public void fixedRateJob(){
        log.info(Thread.currentThread().getName()+":fixedRate 每隔1秒,执行10秒任务");
        try {
            Thread.sleep(1000*10);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

上述代码代码运行结果如下:

图片alt

从运行结果看,可以得到两个结论:一是任务是阻塞的,也就是说一个任务没有执行完,那么下一个任务就不会开始。二是任务都是由同一个线程来完成的,因此阻塞也就可以理解了。

这个测试结果,不是我们期望,因为我们期望的是,我不关心你任务什么时间完成,我在意的是,每秒钟要执行一个任务,那么1分钟就要执行60个任务。那么怎么来实现呢?这就需要引入异步任务了。

2)、如何实现异步任务

  • 启动异步任务的步骤

1: 启动类里面使用@EnableAsync注解开启功能,自动扫描
2: 在要异步任务的类上写@component
3: 在定义异步任务类写@Async(写在类上代表整个类都是异步,在方法加上代表该类异步执行)

  • 示例代码
package com.shenmazong.demonew.task;

import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

@Slf4j
@Component
@EnableScheduling   // 1.开启定时任务
@EnableAsync        // 2.开启多线程
public class AsyncTask {

    //表示每隔1秒
    @Scheduled(fixedRate=1000)
    @Async
    public void fixedRateJob(){
        log.info(Thread.currentThread().getName()+":fixedRate 每隔1秒,执行10秒任务");
        try {
            Thread.sleep(1000*10);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

图片alt

从上图可以看到,终于每隔1秒,就执行了一次任务。而且每个任务是由不同的线程来执行的。

但是细心的朋友也能够发现,线程的数量是有限的,只有8个,也就是说,在默认情况下,最多只能创建8个线程来完成异步任务。这也能够理解,毕竟系统不能无限地去创建线程,否则系统就会耗尽内存,最后导致宕机了。那要怎么解决这个问题呢,这就需要通过优化任务或者使用分布式任务组件来完成了。