作者:Mars酱
声明:本文章由Mars酱原创,部分内容来源于网络,如有疑问请联系本人。
转载:欢迎转载,转载前先请联系我!
前言
JDK自带的Timer是无法做到多任务并发的,那么我们怎么处理多任务定时的并发问题呢?这章节Mars酱来研究下。
Mars酱能想到的就是多线程、线程池这些关键字。
ScheduledExecutorService
ScheduledExecutorService是一个继承ExecutorService的接口类,ExecutorServiceMars酱记得在 Java | 一分钟掌握异步编程 | 3 - 线程异步 - 掘金 (juejin.cn) 提到过,使用多线程实现异步的时候,创建线程池就是用的Executors创建的,这里创建一个任务类型的线程池,我们可以使用:
// mars酱
ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool();
改写前面的例子
我们在前面使用Timer的时候遇到了阻塞,这次我们改用任务线程池来做,改一下前一篇的例子:
import java.util.Date;
import java.util.Timer;
import java.util.TimerTask;
/**
* @author mars酱
*/
public class MarsTimer {
public static void main(String[] args) {
// java.util.Timer timer = new Timer();
// timer.schedule(new TimerTask() {
// @Override
// public void run() {
// System.out.println("当前时间:" + new Date());
// }
// }, 1000, 5000);
// 1. 创建第一个任务,打印时间后延迟5秒
TimerTask tta = new TimerTask() {
@SneakyThrows
@Override
public void run() {
System.out.println(">> 这是a任务:当前时间:" + new Date());
Thread.sleep(5000);
}
};
// 2. 创建第二个任务,直接打印毫秒数
TimerTask ttb = new TimerTask() {
@Override
public void run() {
System.out.println("<< 这是b任务:当前毫秒:" + System.currentTimeMillis());
}
};
// Timer timera = new Timer();
// // 3. 把两个任务都加入计时器中
// timera.schedule(tta, 1000, 5000);
// timera.schedule(ttb, 1000, 5000);
// 3. 创建一个核心线程为5的池
ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(5);
// 4. 塞入第一个任务,5秒的一遍
scheduledExecutorService.scheduleAtFixedRate(tta, 1000, 5000, TimeUnit.MILLISECONDS);
// 5. 塞入第二个任务,也是5秒一遍
scheduledExecutorService.scheduleAtFixedRate(ttb, 1000, 5000, TimeUnit.MILLISECONDS);
}
}
改写之后,运行一下,得到的结果为:
好了,很工整,a和b两个任务都是独立在运行了,完美解决掉了共享队列导致阻塞的问题。
问题来了
如果Mars酱的任务是每个月执行一次,或者生日任务是每年执行一次,怎么办呐?
ScheduledExecutorService中的schedule函数虽然是支持周期单位到天的,但是如果是每周、每月、每年这种任务,我们在下次任务执行的时候,还要自行计算好时间才行,还是有点缺陷,解决办法也有:支持cron表达式就行。但是ScheduledExecutorService提供的方法是不支持cron表达式的。下面是cron表达是的介绍
cron表达式
cron表达式是一个具有时间含义的字符串,字符串以56个空格隔开,分为67个域,格式为X X X X X X X。其中X是一个域的占位符。最后一个代表年份的域非必须,可省略。单个域有多个取值时,使用半角逗号 , 隔开取值。每个域可以是确定的取值,也可以是具有逻辑意义的特殊字符。每个域最多支持一个前导零。比如:
0 10 01 ? * * 2023
表示2023年每天凌晨1点10分执行任务
域占位符的取值
下表为cron表达式中支持的特殊字符,以及含义:
特殊字符 | 含义 | 示例 |
---|---|---|
***** | 所有可能的值。 | 在月域中, ***** 表示每个月;在星期域中, ***** 表示星期的每一天。 |
, | 列出枚举值。 | 在分钟域中,5,20表示分别在5分钟和20分钟触发一次。 |
- | 范围。 | 在分钟域中,5-20表示从5分钟到20分钟之间每隔一分钟触发一次。 |
/ | 指定数值的增量。 | 在分钟域中,0/15表示从第0分钟开始,每15分钟。在分钟域中3/20表示从第3分钟开始,每20分钟。 |
? | 不指定值,仅日期和星期域支持该字符。 | 当日期或星期域其中之一被指定了值以后,为了避免冲突,需要将另一个域的值设为 ? 。 |
L | 单词Last的首字母,表示最后一天,仅日期和星期域支持该字符。说明 指定L字符时,避免指定列表或者范围,否则,会导致逻辑问题。 | - 在日期域中,L表示某个月的最后一天。在星期域中,L表示一个星期的最后一天,也就是星期日(SUN)。 |
- 如果在L前有具体的内容,例如,在星期域中的6L表示这个月的最后一个星期六。 |
| W | 除周末以外的有效工作日,在离指定日期的最近的有效工作日触发事件。W字符寻找最近有效工作日时不会跨过当前月份,连用字符LW时表示为指定月份的最后一个工作日。 | 在日期域中5W,如果5日是星期六,则将在最近的工作日星期五,即4日触发。如果5日是星期天,则将在最近的工作日星期一,即6日触发;如果5日在星期一到星期五中的一天,则就在5日触发。 |
| # | 确定每个月第几个星期几,仅星期域支持该字符。 | 在星期域中,4#2表示某月的第二个星期四。
哪些定时任务支持cron表达式?
Linux操作系统支持cron表达式,Java支持cron表达式的有Spring框架。
到站下车了,下站见。