Java | 一分钟掌握定时任务 | 4 - 多线程的Timer

作者: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框架。

到站下车了,下站见。

猜你喜欢

转载自juejin.im/post/7229374487174889509