Java定时任务之Timer

源自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作为替代方案更好。

参考博文:
1. 简单理解java中timer的schedule和scheduleAtFixedRate方法的区别
2. What is the difference between schedule and scheduleAtFixedRate?
3. Java 并发专题 : Timer的缺陷 用ScheduledExecutorService替代

猜你喜欢

转载自blog.csdn.net/xtick/article/details/81002342
今日推荐