每天早上七点三十,准时推送干货
一、介绍
说到定时任务,相信大家都不陌生,在我们实际的工作中,用到定时任务的场景可以说非常的多,例如:
双 11 的 0 点,定时开启秒杀
每月1号,财务系统自动拉取每个人的绩效工资,用于薪资计算
使用 TCP 长连接时,客户端按照固定频率定时向服务端发送心跳请求
等等,定时器像水和空气一般,普遍存在于各个场景中,在实际的业务开发中,基本上少不了定时任务的应用。
总结起来,一般定时任务的表现有以下几个特征:
1.在某个时刻触发,例如11.11号0点开启秒杀
2.按照固定频率周期性触发,例如每分钟发送心跳请求
3.预约指定时刻开始周期性触发,例如从12.1号开始每天7点闹钟响起
说了这么多,也不BB了,下面我们就来点干活,列举一些实际项目中使用的相关工具!
二、crontab 定时器
2.1、介绍
crontab 严格来说并不是属于 java 内的,它是 linux 自带的一个工具,可以周期性地执行某个shell
脚本或命令。
由于 crontab 在实际开发中应用比较多, 特别是对于运维的人,crontab 命令是必须用到的命令,自动化运维中一定少不了它,而且 crontab 表达式跟我们后面要介绍的其他定时任务框架,例如 Quartz,Spring Schedule 的 cron 表达式类似,所以这里先介绍 crontab。
简而言之,crontab 就是一个自定义定时器。
命令格式如下:
.---------------- minute (0 - 59)
| .------------- hour (0 - 23)
| | .---------- day of month (1 - 31)
| | | .------- month (1 - 12) OR jan,feb,mar,apr ...
| | | | .---- day of week (0 - 6) (Sunday=0 or 7) ...
| | | | |
* * * * * command
第一个参数(minute):代表一小时内的第几分,范围 0-59。
第二个参数(hour):代表一天中的第几小时,范围 0-23。
第三个参数(day):代表一个月中的第几天,范围 1-31。
第四个参数(month):代表一年中第几个月,范围 1-12。
第五个参数(week):代表星期几,范围 0-7 (0及7都是星期天)。
第六个参数(command):所要执行的指令。
具体应用的时候,时间定义大概是长下面这个样子!
# 每5分钟执行一次命令
*/5 * * * * Command
# 每小时的第5分钟执行一次命令
5 * * * * Command
# 指定每天下午的 6:30 执行一次命令
30 18 * * * Command
# 指定每月8号的7:30分执行一次命令
30 7 8 * * Command
# 指定每年的6月8日5:30执行一次命令
30 5 8 6 * Command
# 指定每星期日的6:30执行一次命令
30 6 * * 0 Command
2.2、具体示例
以centOS
操作系统为例,创建一个定时任务,每分钟执行某个指定shell
脚本,过程如下!
首先安装 crond 相关服务
yum -y install cronie yum-cron
编写一个输出当前时间到日志的
shell
脚本
#创建一个test.sh脚本
vim /root/shell/test.sh
#脚本内容如下,将内容输出到file.log文件
echo `date '+%Y-%m-%d %H:%M:%S'` >> /root/shell/file.log
先执行一下脚本,观察内容是否输出到
file.log
文件
sh /root/shell/test.sh
查看日志文件内容
cat /root/shell/file.log
如果出现以下内容,说明运行正常!
接着再来创建一个定时任务,每分钟执行一次
test.sh
#编辑定时任务【删除-添加-修改】
crontab -e
在文件末尾加入如下信息
#每分钟执行一次test.sh脚本
*/1 * * * * sh /root/shell/test.sh
如果想查定时任务是否加入,可以通过如下命令
#查看crontab定时任务
crontab -l
最后就是重启定时任务
##重新载入配置
systemctl reload crond
#重启服务
systemctl restart crond
查看
file.log
文件实时输出内容,观察test.sh
脚本是否被执行
tail -f /root/shell/file.log
从结果上看,运行正常!
如果想查看定时任务日志,可通过如下命令进行查看
tail -f /var/log/cron
如果想移除某个定时任务,直接输入crontab -e
命令,并移除对应的脚本,然后刷新配置、重启服务即可!
如果想深入的了解crontab
使用,可以参考这篇文章,里面总结得比较好, 这里就不再多说了。
三、java 自带的定时器
3.1、Timer
Timer 定时器,由 jdk 提供的java.util.Timer
和java.util.TimerTask
两个类组合实现。
其中TimerTask
表示某个具体任务,而Timer
则是进行调度任务处理。
实现过程也很简单,示例如下:
import java.util.Timer;
import java.util.TimerTask;
public class TimerTest extends TimerTask {
private String jobName;
public TimerTest(String jobName) {
this.jobName = jobName;
}
@Override
public void run() {
System.out.println("execute " + jobName);
}
public static void main(String[] args) {
Timer timer = new Timer();
long delay1 = 1 * 1000;
long period1 = 1 * 1000;
// 从现在开始 1 秒钟之后,每隔 1 秒钟执行一次 job1
timer.schedule(new TimerTest("job1"), delay1, period1);
long delay2 = 2 * 1000;
long period2 = 2 * 1000;
// 从现在开始 2 秒钟之后,每隔 2 秒钟执行一次 job2
timer.schedule(new TimerTest("job2"), delay2, period2);
}
}
输出结果:
execute job1
execute job2
execute job1
execute job1
execute job2
execute job1
execute job1
execute job2
...
Timer 的优点在于简单易用,由于所有任务都是由同一个线程来调度,因此所有任务都是串行执行的,同一时间只能有一个任务在执行,前一个任务的延迟或异常都将会影响到之后的任务。
具体原因如下:
1.当一个线程抛出异常时,整个 Timer 都会停止运行。例如上面的 job1 抛出异常的话,job2 也不会再跑了
当一个线程里面处理的时间非常长的时候,会影响其他 job 的调度。例如,如果 job1 处理的时间要 1 分钟, 那么 job2 至少要等 1 分钟之后才能跑。
基于上面的原因,Timer 现在生产环境中都不在使用!
3.2、ScheduledExecutor
鉴于 Timer 的上述缺陷,从 Java 5 开始,推出了基于线程池设计的 ScheduledExecutor。
其设计思想是,每一个被调度的任务都会由线程池中一个线程来管理执行,因此任务是并发执行的,相互之间不会受到干扰。需要注意的是,只有当任务的执行时间到来时,ScheduedEx