Java & Go 定时任务
定时任务概括
定时任务是软件开发中用于自动执行周期性任务的技术。它允许开发者设置特定的时间点或间隔来触发预定的操作,如数据备份、清理缓存、发送通知等。这种机制可以减少人工干预,提高系统效率和稳定性。定时任务在不同的操作系统和编程环境中有不同的实现方式,例如Linux的cron作业、Windows的任务计划程序,或是编程语言中的相关库。它们帮助自动化重复性工作,确保关键任务按时执行,是现代应用程序不可或缺的组成部分。
定时任务使用场景
定时任务在软件开发当中使用非常广泛。主要有以下场景:
- 数据备份。定时备份重要数据,防止丢失。
- 日志清理。定时归档和清理日志文件,释放磁盘空间
- 性能监控。定时收集、处理和上报性能数据。
- 数据同步。定时将最新数据同步给其他消费者。
- 资源管理。定时清理、回收系统资源,提升利用率和性能。
当然列举的这几个有些宽泛,在实际开发当中,会有多种多样的定时任务场景。下面我们先来看看 Java
语言都有哪些实现定时任务的类库。
Java 语言实现定时任务
相信很多小伙伴接触最多的定时任务就是定时自动化回归测试了。通常会有专门的开发和测试框架来完成具体的设置和执行定时任务。在 Java
语言中,实现定时任务有几种常用的方法:
java.util.Timer
类:这是Java标准库提供的一个类,可以用来安排任务以后在后台线程中执行。使用Timer
类,你可以创建一个TimerTask
任务,然后使用schedule
或scheduleAtFixedRate
方法来安排任务的执行。ScheduledExecutorService
接口:这是Java并发包中的一部分,提供了更灵活的定时任务调度能力。你可以使用Executors
类创建一个ScheduledExecutorService
实例,然后使用schedule
或scheduleAtFixedRate
方法来安排任务。- Spring框架的
@Scheduled
注解:如果你在使用Spring框架,可以利用@Scheduled
注解来简化定时任务的配置。Spring的调度器会根据注解的参数来执行相应的方法。 - Quartz Scheduler:这是一个开源的作业调度库,提供了比Java标准库更强大的定时任务功能。Quartz允许你配置复杂的调度策略,如cron表达式,并支持集群。
java.util.Timer
利用 Spring
框架支持相对来说比较常见。下面我写了一个 java.util.Timer
实现每秒打印一次时间的定时任务的简单案例。可以按以下步骤编写代码:
- 创建一个继承自
TimerTask
的类,在其中实现run
方法。 - 创建一个
Timer
对象。 - 使用
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 1m2s
,time.ParseDuration()
支持的格式都可以用在这里。除此之外 cron
预定义了一些时间规则:
@yearly
:也可以写作@annually
,表示每年第一天的 0 点。等价于0 0 1 1 *
;@monthly
:表示每月第一天的 0 点。等价于0 0 1 * *
;@weekly
:表示每周第一天的 0 点,注意第一天为周日,即周六结束,周日开始的那个 0 点。等价于0 0 * * 0
;@daily
:也可以写作@midnight
,表示每天 0 点。等价于0 0 * * *
;@hourly
:表示每小时的开始。等价于0 * * * *
。
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("任务结束")
}