定时任务总结(Linux crontab / Spring Task / Quartz / TimerTask / ScheduledExecutorService)

定时任务总结(Linux crontab / Spring Task / Quartz)

                                                                                                                                                     ——by zhengkai

什么时候需要使用定时任务?比如你遇到这样的问题:

想每月25号,信用卡自动还款?

想每年情人节自己给当年暗恋的女神发一封匿名贺卡?。

想每隔1小时,备份一下自己最爱的精彩影片、学习笔记到自己的云盘中 ?。

这些问题总结起来就是:在某一个有规律的(周期性的)时间点干某件事。并且时间的触发的条件可以非常复杂(比如每月最后一个工作日的17:50),复杂到需要一个专门的框架来干这个事。本次主要介绍Linux中的定时任务、java原生的定时任务、以及两个任务调度框架:Spring Task和Quartz。

1.Linux中的定时任务

1.1 crond简介

crond是linux下用来周期性的执行某种任务或等待处理某些事件的一个守护进程,与windows下的计划任务类似,当安装完成操作系统后,默认会安装此服务工具, crond进程每分钟会定期检查是否有要执行的任务,如果有要执行的任务,则自动执行该任务。Linux系统上面原本就有非常多的计划性工作,因此这个系统服务是默认启动的。另外, 由于使用者自己也可以设置计划任务,所以,Linux 系统也提供了使用者控制计划任务的命令:crontab命令。

1.1.1 系统任务调度

系统任务调度:系统周期性所要执行的工作,比如写缓存数据到硬盘、日志清理等。在/etc目录下有一个crontab文件,这个就是系统任务调度的配置文件。

cat /etc/crontab

 

1.1.2 用户任务调度

用户定期要执行的工作,比如用户数据备份、定时邮件提醒等。用户可以使用 crontab 工具来定制自己的计划任务。所有用户定义的crontab 文件都被保存在 /var/spool/cron目录中。其文件名与用户名一致。即:/var/spool/cron/[用户名]

用户所建立的crontab文件中,每一行都代表一项任务,每行的每个字段代表一项设置,它的格式共分为六个字段,前五段是时间设定段,第六段是要执行的命令段,格式如下:

minute hour day month week command

其中:

minute: 表示分钟,可以是从0到59之间的任何整数。

hour:表示小时,可以是从0到23之间的任何整数。

day:表示日期,可以是从1到31之间的任何整数。

month:表示月份,可以是从1到12之间的任何整数。

week:表示星期几,可以是从0到7之间的任何整数,这里的0或7代表星期日。

command:要执行的命令,可以是系统命令,也可以是自己编写的脚本文件。

在以上各个字段中,还可以使用以下特殊字符:

* :          表示任意的时刻;如小时位 * 则表示每个小时

n :         表示特定的时刻;如小时位 5 就表示5时

n,m :     表示特定的几个时刻;如小时位 1,10 就表示1时和10时

n-m :    表示一个时间段;如小时位 1-5 就表示1到5点

*/n :         表示每隔多少个时间单位执行一次;如小时位 */1 就表示每隔1个小时执行一次命令,也可以写成 1-23/1。

1.1.3 crontab配置实例

* 1 * * * /opt/script/backup.sh                    //从1:0到1:59 每隔1分钟 执行

15 05 * * * /opt/script/backup.sh               //05:15 执行

*/10 * * * * /opt/script/backup.sh               //每隔10分 执行

0 17 * * 1 /opt/script/backup.sh                //每周一的 17:00 执行

2 8-20/3 * * * /opt/script/backup.sh           //8:02,11:02,14:02,17:02,20:02 执行

 

30 21 * * * /etc/init.d/nginx restart                  //每晚的21:30重启 nginx。

45 4 1,10,22 * * /etc/init.d/nginx restart         //每月1、 10、22日的4 : 45重启nginx。

10 1 * * 6,0 /etc/init.d/nginx restart                //每周六、周日的1 : 10重启nginx。

0,30 18-23 * * * /etc/init.d/nginx restart         //每天18 : 00至23 : 00之间每隔30分钟重启nginx。

0 23 * * 6 /etc/init.d/nginx restart                  //每星期六的11 : 00 pm重启nginx。

* */1 * * * /etc/init.d/nginx restart                   //每一小时重启nginx

* 23-7/1 * * * /etc/init.d/nginx restart             //晚上11点到早上7点之间,每 隔一小时重启nginx

0 11 4 * mon-wed /etc/init.d/nginx restart       //每月的4号与每周一到周三 的11点重启nginx

0 4 1 jan * /etc/init.d/nginx restart                //一月一号的4点重启nginx

*/30 * * * * /usr/sbin/ntpdate 210.72.145.20    //每半小时同步一下时间

1.2 crond服务管理

安装crontab:

yum install crontabs

注意:CentOs中默认已经安装了crontabs服务,并已经设置了开机启动,无需再次安装和配置。

服务操作说明:

/sbin/service crond start   //启动服务

/sbin/service crond stop   //关闭服务

/sbin/service crond restart  //重启服务

/sbin/service crond reload  //重新载入配置

/sbin/service crond status  //启动服务

1.3 crontab命令详解

crontab [ -e | -l | -r ]

命令功能:

通过crontab 命令,我们可以直接打开并编辑当前登录用户的crond配置文件而不必再自己查找配置文件所在的位置,大大方便了用户操作。

-e:编辑某个用户的crontab文件内容。如果不指定用户,则表示编辑当前用户的crontab文件。

-l:显示某个用户的crontab文件内容,如果不指定用户,则表示显示当前用户的crontab文件内容。

-r:从/var/spool/cron目录中删除某个用户的crontab文件,如果不指定用户,则默认删除当前用户的crontab文件。

-i:在删除用户的crontab文件时给确认提示。

1.4 crontab实战

1.准备运行脚本

echo "٩()۶ ====== `date +"%Y-%m-%d %H:%M:%S"`" >> /root/script/test/list.txt

注意:脚本需要有运行权限,需要使用chmod 755 [文件名]命令赋予运行权限。

2.使用crontab -i 命令打开自己的配置文件。

3.加入自己的配置

4.查看效果

2.java原生定时任务

2.1使用Timer

Timer由于其固有缺陷(Timer不支持多线程,任务是串行的,而且也不捕获异常,假设某个任务异常了,整个Timer就无法运行了。)现在使用较少,了解即可。

2.1.1 代码示例

public class TimerTest {

    @Test

    public void testTimer() throws IOException {

        //准备定时任务

        TimerTask  timerTask1 = new TimerTask() {

            @Override

            public void run() {

                System.out.println(Thread.currentThread().getName() + "------>  ٩(๑❛ᴗ❛๑)۶ ===timerTask1=== " + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));

            }

        };

        TimerTask  timerTask2 = new TimerTask() {

            @Override

            public void run() {

                System.out.println(Thread.currentThread().getName() + "------>  ٩(๑❛ᴗ❛๑)۶ ===timerTask2=== " + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));

            }

        };

        //创建定时器

        Timer timer = new Timer();

        //执行定时任务,同一个Timer的任务使用同一个线程

        timer.schedule(timerTask1,0,1000);

        timer.schedule(timerTask2,0,1000);

        //保存当前窗口

        System.in.read();

    }

}

 

2.1.2 运行效果

可以看到,两个任务是在同一个线程中执行的。

2.2使用ScheduledExecutorService

ScheduledExecutorService是基于线程池设计的定时任务类,每个调度任务都会分配到线程池中的一个线程去执行,也就是说,任务是并发执行,互不影响。需要注意,只有当调度任务来的时候,ScheduledExecutorService才会真正启动一个线程,其余时间ScheduledExecutorService都是出于轮询任务的状态。

2.2.1 代码示例

public class ExecutorServiceTest {

    @Test

    public void testExecutorService() throws IOException {

        //准备支持线程池的定时任务服务

        ScheduledExecutorService executorService = Executors.newScheduledThreadPool(5);

        //将任务加入服务

        executorService.scheduleAtFixedRate(()->{

            System.out.println(Thread.currentThread().getName() + "------>  ٩(๑❛ᴗ❛๑)۶ ====== " + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));

        },0,1, TimeUnit.SECONDS);

        executorService.scheduleAtFixedRate(()->{

            System.out.println(Thread.currentThread().getName() + "------>  ٩(๑❛ᴗ❛๑)۶ ====== " + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));

        },0,1, TimeUnit.SECONDS);

 

        //保存当前窗口

        System.in.read();

    }

}

2.2.2 运行效果

3.SpringTask

3.1 pom依赖配置

pom文件里面并不需要引入什么依赖,Spring默认提供。

3.2 单线程执行

3.2.1定义单线程测试启动类,使用@EnableScheduling注解启用定时功能

@SpringBootApplication(scanBasePackages = {"com.zk.springtask.singlethreadtask"})

@EnableScheduling

public class SingleThreadSchedulerConfig {

    public static void main( String[] args ) {

        SpringApplication.run(SingleThreadSchedulerConfig.class,args);

    }

}

 

3.2.2 创建单线程定时任务测试类

@Component

public class SingleThreadTestTask {

    @Scheduled(cron = "0/5 * * * * ?")

    private void process1() {

        System.out.println(

                Thread.currentThread().getName() +

                        "٩(๑❛ᴗ❛๑)۶ ===使用cron=== " +

                        LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));

    }

 

    @Scheduled(fixedRate = 5000)

    private void process2() {

        System.out.println(

                Thread.currentThread().getName() +

                        "٩(๑❛ᴗ❛๑)۶ ===使用fixedRate=== " +

                        LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));

    }

 

    @Scheduled(fixedDelay = 5000)

    private void process3() {

        System.out.println(

                Thread.currentThread().getName() +

                        "٩(๑❛ᴗ❛๑)۶ ===使用fixedDelay=== " +

                        LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));

    }

 

}

3.2.3 运行效果

3.3 多线程执行

3.3.1 定义多线程测试启动类,需提供一个用于运行定时任务的线程池。

Spring Task默认单线程同步执行,如果需要使用多线程执行需要提供一个自定义线程池或者是一个Spring提供的TaskScheduler类的对象。ThreadPoolTaskScheduler是SpringTask的核心实现类,该类提供了大量的重载方法进行任务调度。

@SpringBootApplication(scanBasePackages = {"com.zk.springtask.multithreadtask"})

@EnableScheduling

public class MultiThreadSchedulerConfig {

    public static void main(String[] args) {

        SpringApplication.run(MultiThreadSchedulerConfig.class, args);

    }

 

    @Bean

    public TaskScheduler provideTaskScheduler() {

        ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();

        //线程池大小

        scheduler.setPoolSize(10);

        //线程名字前缀

        scheduler.setThreadNamePrefix("spring-multithreadtask-thread");

        return scheduler;

    }

}

 

3.3.2 创建多线程定时任务测试类

@Component

public class MultiThreadTestTask {

    @Scheduled(cron="* * * * * ?")

    private void process(){

        System.out.println(

          Thread.currentThread().getName() +

          "٩(๑❛ᴗ❛๑)۶ ====== " +

          LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));

    }

}

3.3.3 运行效果

3.4 执行时间配置

在上面的定时任务中,我们在方法上使用@Scheduled注解来设置任务的执行时间,并且使用三种属性配置方式:

(1)cron:cron表达式,指定任务在特定时间执行;

(2)fixedDelay:表示上一次任务执行完成后多久再次执行,参数类型为long,单位ms;

(3)fixedDelayString:与fixedDelay含义一样,只是参数类型变为String;

(4)fixedRate:表示按一定的频率执行任务,参数类型为long,单位ms;

(5)fixedRateString: 与fixedRate的含义一样,只是将参数类型变为String;

(6)initialDelay:表示延迟多久再第一次执行任务,参数类型为long,单位ms;

(7)initialDelayString:与initialDelay的含义一样,只是将参数类型变为String;

(8)zone:时区,默认为当前时区,一般没有用到。

3.5 SpringTask中的cron表达式详解

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

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

注意:springboot中不支持年份的配置

注意:cron表达式可以使用工具生成。在线cron表达式生成:http://qqe2.com/cron/index

4. quartz

Quartz是一个任务调度框架,通过触发器设置作业的定时运行规则,来执行定时任务。如果SpringBoot版本是2.0.0以后的,则在spring-boot-starter中已经包含了quart的依赖,则可以直接使用spring-boot-starter-quartz依赖。由于SpringBoot 2.0版本之前的配置过于复杂,并且已经过时,这里不在介绍。

4.1 核心对象和简单架构

Quartz中主要包括以下核心对象:

1.Scheduler核心调度器:负责定期定时定频率的去执行任务

2.Job任务:包括了业务逻辑

3.JobDetail任务描述

4.Trigger-触发器:定义让任务生效的时间

核心对象之间的关系如下图所示:

4.2 pom依赖配置

<dependency>

         <groupId>org.springframework.boot</groupId>

         <artifactId>spring-boot-starter-quartz</artifactId>

</dependency>

 

4.3创建任务类QuartzTask,该类主要是继承了QuartzJobBean

public class QuartzTask extends QuartzJobBean{

    /**

     * 定时任务配置

     * @param jobExecutionContext

     * @throws JobExecutionException

     */

    @Override

    protected void executeInternal(JobExecutionContext jobExecutionContext) throws JobExecutionException {

        System.out.println(

                Thread.currentThread().getName() +

                        "٩(๑❛ᴗ❛๑)۶ ====== " +

                        LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));

    }

}

 

4.4创建配置类QuartzConfig

@SpringBootApplication

@EnableScheduling

public class QuartzConfig {

    /**

     * 创建一个job

     * @return

     */

    @Bean

    public JobDetail testQuartzTask(){

        return JobBuilder.newJob(QuartzTask.class).withIdentity("quartzTask").storeDurably().build();

    }

 

    /**

     * 创建一个触发器构建者

     * @return

     */

    @Bean

    public Trigger testQuartzTrigger(){

        SimpleScheduleBuilder scheduleBuilder = SimpleScheduleBuilder.simpleSchedule()

                .withIntervalInSeconds(1)  //设置时间周期单位秒

                .repeatForever();

        return TriggerBuilder.newTrigger().forJob(testQuartzTask())

                .withIdentity("quartzTask")

                .withSchedule(scheduleBuilder)

                .build();

    }

 

    public static void main(String[] args) {

        SpringApplication.run(QuartzConfig.class, args);

    }

}

 

4.5运行效果

5. Quartz与Spring Task对比总结

5.1 Quartz 和 Spring Task执行时间对比

1. Quartz设置同步模式时:一个任务的两次执行的时间间隔是:“执行时间”和“trigger的设定间隔”的最大值。

2. Task默认同步模式:一个任务的两次执行的时间间隔是:“执行时间”+“trigger的设定间隔”,即一个任务完成执行后,才开始trigger计时。

 

5.2 Quartz 特点

1. 默认多线程异步执行。

2. 一个任务在上一次调度未完成执行,下一次调度时间到时,会另起一个线程开始新的调度。在业务繁忙时,一个任务或许会有多个线程在执行,导致数据处理异常。

3. 单任务同步:配置属性,可以使一个任务的一次调度在未完成时,而不会开启下一次调度。

4. 多个任务同时运行,任务之间没有直接的影响,多任务执行的快慢取决于CPU的性能。

5. SchedulerFactoryBean不能使用注解来配置。还是我没找到注解的方法。

5.3 Spring Task特点

1. 默认单线程同步执行。

2. 一个任务执行完上一次之后,才会执行下一次调度。

3. 多任务之间按顺序执行,一个任务执行完成之后才会执行另一个任务。

4. 多任务并行执行需要设置线程池。

5. 全程可以通过注解配置。

声明:本文参考了大量前辈们的博客文章,感谢他们的分享让我学到了更多。由于参考内容繁杂,如您发现您的文章并未出现在参考列表中,请通知我添加,谢谢!。

参考列表:

https://www.cnblogs.com/intval/p/5763929.html

http://www.wanqhblog.top/2018/02/01/SpringBootTaskSchedule/

https://yq.aliyun.com/ziliao/366191

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值