Java定时任务

源自icp云硬盘项目。当时保存了许多有关Timer和ScheduledExecutorService相关的书签,整理一下,删一删书签。

使用ScheduledExecutorService而不是Timer

原因可以总结为一下几点:

  1. Timer是单线程的,如果存在多个任务,且任务时间过长,超过了两个任务的时间间隔,那下个任务的执行可能不会按照预期的时间执行。
  2. 当Timer中的某个任务抛出RuntimeException,Timer会停止所有任务的运行。
  3. Timer执行周期任务时依赖系统时间。

由于ScheduledExecutorService是多线程的,所以它可以避免上述的所有问题。

Timer的schedule和scheduleAtFixedRate

这个部分可以参看What is the difference between schedule and scheduleAtFixedRate?
翻译过来就是:
假设你期望周期5s,每个任务执行2s,那么有如下一个任务:

TTWWWTTWWWTTWWWTT //T=task,W=wait,每个字母代表执行1s

经测试,这时无论使用schedule还是scheduleAtFixedRate都是相同结果。
假设在执行任务过程中发生了GC,以G代表,那么以schedule方法执行如下:

TTWWWGGTTWWWTTWWWTT //G=GC

以scheduleAtFixedRate执行如下:

TTWWWGGTTWTTWWWTT //G=GC

可以看到,在第三次执行的时候W减少了,目的是为了追赶上(catch up)预期的执行时间。如果GC时间比较久的话,那么scheduleAtFixedRate会记录多个需要catch up的任务。

public class Test {
    public static long start;
    public static void main(String[] args) {
        start=System.currentTimeMillis();
        TimerTask task1=new TimerTask() {
            @Override
            public void run() {
                System.out.println("任务1开始执行:"+(System.currentTimeMillis()-start)+"-----");
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("任务1结束执行:"+(System.currentTimeMillis()-start));
            }
        };
        TimerTask task2=new TimerTask() {
            @Override
            public void run() {
                System.out.println("任务2开始执行:"+(System.currentTimeMillis()-start)+"-----");
                try {
                    Thread.sleep(4000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("任务2结束执行:"+(System.currentTimeMillis()-start));
            }
        };
        Timer timer=new Timer();
        //timer.schedule(task1,0,5000);//A
        timer.scheduleAtFixedRate(task1,0,5000);//B
        timer.schedule(task2,2000);
        //timer.schedule(task2,1000);
    }
}

选用注释中A方法的时候,结果如下:

任务1开始执行:4-----
任务1结束执行:2004
任务2开始执行:2004-----
任务2结束执行:6005
任务1开始执行:6005-----
任务1结束执行:8005
任务1开始执行:11005----- //第三次任务
任务1结束执行:13005
任务1开始执行:16006-----
任务1结束执行:18007

选用注释中B方法的时候,结果如下:

任务1开始执行:7-----
任务1结束执行:2015
任务2开始执行:2016-----
任务2结束执行:6017
任务1开始执行:6017-----
任务1结束执行:8017
任务1开始执行:10001-----  //第三次任务
任务1结束执行:12002
任务1开始执行:15001-----
任务1结束执行:17001

通过对比可以发现,在执行第二次task1的之前,由于task2的插入,破坏了at fixed rate这个条件,即按照上次任务开始时间为起点,在第二次执行的时候被Timer记录了
说了这么多废话,其实还是用ScheduledExecutorService作为替代方案更好。

在SpringBoot的环境

使用@scheduled注解即可使用定时任务,而且一定要注意@Async标签的使用,不然一个线程若为死循环,那么下一个定时的任务,到了执行时间依旧不会执行。


参考文章:

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值