cron4j表达式与使用简介

cron4j是一个轻量级的Java任务调度工具。

引入Jar包:

<dependency>
<groupId>it.sauronsoftware.cron4j</groupId>
<artifactId>cron4j</artifactId>
<version>2.2.5</version>
</dependency>


cron4j的cron表达式最多只允许5个部分,每个部分用空格分隔开,从左至右分别表示“分”、“时”、“天”、“月”、“周”,具体规则如下:

分:取值从 0 到 59

时:取值从 0 到 23

天:取值从 1 到 31,字母 L 可用于表示月的最后一天

月:取值从 1 到 12,可以用别名表示:jan、feb、mar、apr、may、jun、jul、aug、sep、oct、nov、dec

周:取值从 0 到 6,0表示周日,6表示周六,可以用别名表示:sun、mon、tue、wed、thu、fri、sat

以上5个部分的分、时、天、月、周又分别支持如下字符:

数字 n :表示一个具体的时间点,例如 5 * * * * 表示 5 分这个时间点时执行

逗号 , :表示指定多个数值,例如 3,5 * * * * 表示 3 和 5 分这两个时间点执行

减号 - :表示范围,例如 1-3 * * * * 表示 1 分、2 分再到 3 分这三个时间点执行

星号 * :表示每一个时间点,例如 * * * * * 表示每分钟执行

除号 / :表示指定一个值的增加幅度。例如 */5表示每隔5分钟执行一次(序列:0:00, 0:05, 0:10, 0:15 等等)


常见错误:

cron4j在表达式中使用除号指定增加幅度时与linux稍有不同。例如在linux中表达式 10/3 * * * * 的含义是从第10分钟开始,每隔三分钟调度一次,而在cron4j中需要使用 10-59/3 * * * * 来表达。

Scheduler scheduler = new Scheduler();
//写法一:此种方式,控制台每分钟打印
scheduler.schedule("10-59/1 * * * *", () -> System.out.println("Every Minute Run."));
//写法二:此种方式,控制台不会有任何打印
scheduler.schedule("10/1 * * * *", () -> System.out.println("Every Minute Run."));
scheduler.start();
try {
    Thread.sleep(1000L * 60L * 10L);
} catch (InterruptedException e) {
    e.printStackTrace();
}
scheduler.stop();


两大疑问:

第一个疑问是当某个任务调度抛出了异常,那么这个任务在下次被调度的时间点上还会不会被调度,答案是肯定的,不管什么时候出现异常,时间一到调度仍然会被执行。

Scheduler scheduler = new Scheduler();
scheduler.schedule("*/1 * * * *", () -> {
    System.out.println("Every Minute Run At: " + new Date());
    throw new RuntimeException("任务调度抛出异常");
});
scheduler.start();
try {
    Thread.sleep(1000L * 60L * 10L);
} catch (InterruptedException e) {
    e.printStackTrace();
}
scheduler.stop();

打印结果如下:

Every Minute Run At: Wed Feb 13 10:09:00 CST 2019
java.lang.RuntimeException: 任务调度抛出异常
at com.tinytime.demo.cron4j.Demo2.lambda$main$0(Demo2.java:17)
at it.sauronsoftware.cron4j.RunnableTask.execute(Unknown Source)
at it.sauronsoftware.cron4j.TaskExecutor$Runner.run(Unknown Source)
at java.lang.Thread.run(Thread.java:748)
Every Minute Run At: Wed Feb 13 10:10:00 CST 2019
java.lang.RuntimeException: 任务调度抛出异常
at com.tinytime.demo.cron4j.Demo2.lambda$main$0(Demo2.java:17)
at it.sauronsoftware.cron4j.RunnableTask.execute(Unknown Source)
at it.sauronsoftware.cron4j.TaskExecutor$Runner.run(Unknown Source)
at java.lang.Thread.run(Thread.java:748)

由此可见,即使上一次调度任务发生异常,下次任务到了时间仍然会被调度。

第二个疑问是假如某个任务执行时间很长,如果这个任务上次调度后直到本次调度到来的时候还没执行完,那么本次调度是否还会进行,答案也是肯定的。

Scheduler scheduler = new Scheduler();
scheduler.schedule("*/1 * * * *", () -> {
    System.out.println("开始调度任务...... At: " + new Date());
    try {
        //等待两分钟
        Thread.sleep(1000L * 60L * 2L);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    System.out.println("Every Minute Run At: " + new Date());
});
scheduler.start();
try {
    Thread.sleep(1000L * 60L * 10L);
} catch (InterruptedException e) {
    e.printStackTrace();
}
scheduler.stop();

打印结果如下:

开始调度任务...... At: Wed Feb 13 10:22:00 CST 2019
开始调度任务...... At: Wed Feb 13 10:23:00 CST 2019
开始调度任务...... At: Wed Feb 13 10:24:00 CST 2019
Every Minute Run At: Wed Feb 13 10:24:00 CST 2019
开始调度任务...... At: Wed Feb 13 10:25:00 CST 2019
Every Minute Run At: Wed Feb 13 10:25:00 CST 2019

由此可见,即使上一次调度任务没有执行完成,下次任务到了时间仍然会被调度。

总结:每次调度都是独立的,上次调度是否抛出异常、是否执行完,都与本次调度无关。


线程调度:

public class Quickstart {

    public static void main(String[] args) {
        Scheduler scheduler = new Scheduler();
        scheduler.schedule("* * * * *", new Runnable() {
            @Override
            public void run() {
                System.out.println("Every Minute Run.");
            }
        });
        scheduler.start();

        try {
            Thread.sleep(1000L * 60L * 10L);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        scheduler.stop();
    }
}


系统进程调度:

public class ProcessJob {

    public static void main(String[] args) {
        ProcessTask task = new ProcessTask("C:\\Windows\\System32\\notepad.exe");
        Scheduler scheduler = new Scheduler();
        scheduler.schedule("* * * * *", task);
        scheduler.start();
    }
}


参考资料:

1、Cron4jPlugin:http://www.jfinal.com/doc/9-2

2、官网:http://www.sauronsoftware.it/projects/cron4j/manual.php

评论区

ihss23

2019-02-12 14:55

我就是没搞清你说的这个常见错误,最后我只能用了枚举。

JFinal

2019-02-12 16:15

两大疑问的第二个,答案是否定的,你再仔细试下

感谢分享

小小魔炎

2019-02-13 08:59

每次调度都是独立的√
上次调度是否抛出异常、是否执行完,与本次调度是否有关,这个应该允许业务配置,故做可配置化处理会更好一点吧

javagoboy

2019-02-13 10:16

@小小魔炎 你想要什么效果?

javagoboy

2019-02-13 10:17

@ihss23 补充了相关验证代码,10/1 * * * *是不起作用的。

javagoboy

2019-02-13 10:28

@JFinal 补充了相关验证代码,经验证,仍认为答案是肯定的,不知波总所说的是什么场景下答案是否定的,请指教,谢谢。

小小魔炎

2019-02-13 10:38

@javagoboy 哦。。。我误解了之前的意思。。。我是想说,可以通过额外增加一些try catch、静态boolean来控制,抛出异常后可继续或不可继续调度,未执行完可继续或不可继续调度这样的效果

javagoboy

2019-02-13 10:43

@小小魔炎 你说的“可继续和不可继续”是针对本次调度任务,还是下次调度任务?正常来讲调度任务是独立的,抛出异常不会相互影响,在try-catch时,如果不想继续调度了,应该可以在catch语句块中手动执行scheduler.shutdown()来结束调度吧。

javagoboy

2019-02-13 10:46

@小小魔炎 写错了,是scheduler.stop();

JFinal

2019-02-15 21:19

@javagoboy 前面说的“否定”是指:当一个 task 的调度调用执行时间很长,直到下次调度它的时机到来时该 task 还没执行完成,那么本次调度将跳过,并不会重新建立新的 task 进行调度

测试的时候在 task 中弄个 static 变量很容易测试

热门分享

扫码入社