定时任务概括

定时任务是软件开发中用于自动执行周期性任务的技术。它允许开发者设置特定的时间点或间隔来触发预定的操作,如数据备份、清理缓存、发送通知等。这种机制可以减少人工干预,提高系统效率和稳定性。定时任务在不同的操作系统和编程环境中有不同的实现方式,例如 Linux 的 cron 作业、Windows 的任务计划程序,或是编程语言中的相关库。它们帮助自动化重复性工作,确保关键任务按时执行,是现代应用程序不可或缺的组成部分。

定时任务使用场景

定时任务在软件开发当中使用非常广泛。主要有以下场景:

  1. 数据备份。定时备份重要数据,防止丢失。
  2. 日志清理。定时归档和清理日志文件,释放磁盘空间
  3. 性能监控。定时收集、处理和上报性能数据。
  4. 数据同步。定时将最新数据同步给其他消费者。
  5. 资源管理。定时清理、回收系统资源,提升利用率和性能。

当然列举的这几个有些宽泛,在实际开发当中,会有多种多样的定时任务场景。下面我们先来看看 Java 语言都有哪些实现定时任务的类库。

Java 语言实现定时任务

相信很多小伙伴接触最多的定时任务就是定时自动化回归测试了。通常会有专门的开发和测试框架来完成具体的设置和执行定时任务。在 Java 语言中,实现定时任务有几种常用的方法:

  1. java.util.Timer:这是 Java 标准库提供的一个类,可以用来安排任务以后在后台线程中执行。使用Timer类,你可以创建一个TimerTask任务,然后使用schedulescheduleAtFixedRate方法来安排任务的执行。
  2. ScheduledExecutorService 接口:这是 Java 并发包中的一部分,提供了更灵活的定时任务调度能力。你可以使用Executors类创建一个ScheduledExecutorService实例,然后使用schedulescheduleAtFixedRate方法来安排任务。
  3. Spring 框架的@Scheduled注解:如果你在使用 Spring 框架,可以利用@Scheduled注解来简化定时任务的配置。Spring 的调度器会根据注解的参数来执行相应的方法。
  4. Quartz Scheduler:这是一个开源的作业调度库,提供了比 Java 标准库更强大的定时任务功能。Quartz 允许你配置复杂的调度策略,如 cron 表达式,并支持集群。

java.util.Timer

利用 Spring 框架支持相对来说比较常见。下面我写了一个 java.util.Timer 实现每秒打印一次时间的定时任务的简单案例。可以按以下步骤编写代码:

  1. 创建一个继承自TimerTask的类,在其中实现run方法。
  2. 创建一个Timer对象。
  3. 使用Timer对象的schedule方法安排任务。

以下是具体的示例代码:


package com.funtest.temp;


import com.funtester.frame.SourceCode;

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Timer;
import java.util.TimerTask;

public class FunTester extends SourceCode {

    public static void main(String[] args) {
        Timer timer = new Timer();// 实例化Timer类
        TimerTask task = new TimerTask() {
            @Override
            public void run() {// 实例化TimerTask类
                // 任务代码,打印当前时间,并指定线程名称
                SimpleDateFormat formatter = new SimpleDateFormat("HH:mm:ss");
                Date date = new Date(System.currentTimeMillis());
                System.out.println(Thread.currentThread().getName() + "      " + formatter.format(date));
            }
        };
        // 0表示立即执行,1000表示每隔1秒执行一次
        timer.scheduleAtFixedRate(task, 0, 1000);
    }

}

下面是控制台打印的信息:

Timer-0      10:04:07
Timer-0      10:04:08
Timer-0      10:04:09
Timer-0      10:04:10

使用 java.util.Timer 实现定时任务,虽然具有简单易用优点,但在我的经验范围内极少,大多数都是包装成服务化,使用 Spring 自带的定时任务执行。其主要缺点就是:单线程执行、异常处理不够优雅、不支持并发。总体来讲不如 ScheduledExecutorService 功能强大。

ScheduledExecutorService

下面是 ScheduledExecutorService 的简单案例。

package com.funtest.temp;  


import com.funtester.frame.SourceCode;  

import java.text.SimpleDateFormat;  
import java.util.Date;  
import java.util.concurrent.Executors;  
import java.util.concurrent.ScheduledExecutorService;  
import java.util.concurrent.TimeUnit;  

public class FunTester extends SourceCode {  

    public static void main(String[] args) {  
        // 创建一个具有单个线程的调度程序  
        ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);  
        // 创建一个Runnable任务,每秒打印一次当前时间  
        Runnable task = new Runnable() {  
            @Override  
            public void run() {  
                SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss");  
                System.out.println(Thread.currentThread().getName() + "      " + sdf.format(new Date()));  
            }  
        };  
        // 从现在开始1秒钟之后,每隔1秒钟执行一次  
        scheduler.scheduleAtFixedRate(task, 0, 1, TimeUnit.SECONDS);  
    }  

}

明显看到用到了线程池的使用,有点不用多说,简洁性我感觉不输 java.util.Timer

Go 语言定时任务

Go 语言定时任务实现,也是比较简单的也是利用 Go SDK 自带的 time 包。下面是简单的案例:


package main

import (
    "fmt"
    "time"
)

func main() {
    // 创建一个定时器,每隔1秒钟执行一次
    ticker := time.NewTicker(1 * time.Second)
    // 延迟关闭定时器
    defer ticker.Stop()
    // 无限循环
    for {
        // 从定时器的通道中接收数据
        select {
        case t := <-ticker.C:
            fmt.Println("Current time:", t)
        }
    }
}

这个案例基本是最佳实现,如果大家有需求都可以参考,而且 Go 语言 goroutine 的原因,不用很关心协程性能的问题,我自己就直接使用 goroutine 直接起定时任务了。这种对于复杂的定时任务,这种方式就显得力不从心了。

Go 语言 time 包里面还有一种方法实现:

package main  

import (  
    "fmt"  
    "time")  

func main() {  
    // 创建一个定时器,1秒后触发  
    timer := time.AfterFunc(1*time.Second, func() {  
       fmt.Println("Current time:", time.Now())  
    })  

    // 防止程序退出  
    select {  
    case <-timer.C:  
    }  
}

语法上看好像区别不是很大,各位自己选用吧。

cron 包

下面分享一下 Go 第三方包的实现。cron 是一个在 Go 语言中实现定时任务的流行库,它允许你使用 cron 表达式来定义任务的执行时间。PS:这个 cron 目测只支持了分钟级别的。

package main  

import (  
    "fmt"  
    "time"  
    "github.com/robfig/cron/v3")  

func main() {  
    // 创建一个定时任务  
    c := cron.New()  
    // 添加一个定时任务,每隔1分钟执行一次  
    c.AddFunc("0/1 * * * *", func() {  
       fmt.Println("Current time:", time.Now())  
    })  
    // 启动定时任务  
    c.Start()  
    // 防止程序退出  
    select {}  
}

AddFunc 方法参数 spec 不仅支持 cron 语法,为了方便使用还增加了一个 @every 语法,后面可以跟类似 @every 1m2stime.ParseDuration() 支持的格式都可以用在这里。除此之外 cron 预定义了一些时间规则:

gocron 包

还有一个更加灵活的库 gocron 功能更加强大,但使用起来存在一定门槛。下面是一个案例:

package main

import (
    "fmt"
    "time"

    "github.com/go-co-op/gocron"
)

func main() {
    // 定时任务
    s := gocron.NewScheduler(time.Local)
    // 每秒执行一次
    s.Every(1).Minutes().Do(func() {
        fmt.Println("Current time:", time.Now())
    })
    s.StartBlocking()
    fmt.Println("任务结束")
}

gocron 设置时间的话,都可以通过调用不同的 API 来组装实现。对于 API 学习需要一定的学习成本。同时 gocron 也支持 cron 语法的。下面是个例子:

package main

import (
    "fmt"
    "time"

    "github.com/go-co-op/gocron"
)

func main() {
    // 定时任务
    s := gocron.NewScheduler(time.Local)
    // 添加一个每周二上午10点每分钟执行一次的任务,使用 cron 表达式
    s.Cron("0 * 10 ? * 2").Do(func() {
        fmt.Println("Task executed at:", time.Now())
    })
    s.StartBlocking()
    fmt.Println("任务结束")
}


↙↙↙阅读原文可查看相关链接,并与作者交流