JAVA Timer 和 TimerTask 的缺点

缺点

Java的定时调度可以通过 Timer 和 TimerTask 来实现,但存在以下缺点:

  • Timer是 单线程运行,即:串行,相互影响。也就是说,对于同一个 Timer 里的多个 TimerTask 任务,如果一个 TimerTask 任务在执行中,其它 TimerTask 即使到达执行时间,也只能 排队等待。

  • 当 Timer 中的一个任务 抛出异常时,会导致其他所有任务 不再执行

阿里开发手册

阿里开发手册 也推荐使用 ScheduledExecutorService

验证第一个缺点

Task1任务类

该类继承 TimerTask 类,实现 run() 方法,该方法中打印 开始时间 和 结束时间,中间注释的部分先不用看

package timerstd;

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.TimerTask;

public class Task1 extends TimerTask {

    @Override
    public void run() {
        String printTime = new SimpleDateFormat("yy-MM-dd HH-mm-ss").format(new Date());
        System.out.println("任务1开始时间:" + printTime);

//        try {
//            System.out.println("休眠5秒");
//            Thread.sleep(5000);
//        } catch (InterruptedException e) {
//            e.printStackTrace();
//        }
        printTime = new SimpleDateFormat("yy-MM-dd HH-mm-ss").format(new Date());
        System.out.println("任务1结束时间:" + printTime);
    }
}

Task2任务类

该类与 Task1 类几乎一样,只是没有中间注释掉的部分

package timerstd;

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.TimerTask;

public class Task2 extends TimerTask {

    @Override
    public void run() {
        String printTime = new SimpleDateFormat("yy-MM-dd HH-mm-ss").format(new Date());
        System.out.println("任务2开始时间:" + printTime);

        printTime = new SimpleDateFormat("yy-MM-dd HH-mm-ss").format(new Date());
        System.out.println("任务2结束时间:" + printTime);
    }
}

主启动类

创建一个 Timer 类对象,并立即启动 Task1 和 Task2 任务

注意: 必须是 同一个Timer 类对象,上面介绍缺点时有提到

package timerstd;

import java.util.Timer;
import java.util.TimerTask;

public class TimerTest {

    public static void main(String[] args) {
        Timer timer = new Timer();

        TimerTask tt1 = new Task1();
        TimerTask tt2 = new Task2();

        // 立即执行
        timer.schedule(tt1,0);
        timer.schedule(tt2,0);
    }
}

执行结果:

任务1开始时间:24-03-07 22-20-29
任务1结束时间:24-03-07 22-20-29
任务2开始时间:24-03-07 22-20-29
任务2结束时间:24-03-07 22-20-29

能够看到两个任务是同一时刻执行的

改造 Task1 类

将注释的部分去掉,延迟 5秒钟,模拟处理业务耗时(处理业务如:查询数据库并处理数据、发邮件、发短信。。。)

package timerstd;

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.TimerTask;

public class Task1 extends TimerTask {

    @Override
    public void run() {
        String printTime = new SimpleDateFormat("yy-MM-dd HH-mm-ss").format(new Date());
        System.out.println("任务1开始时间:" + printTime);

        try {
            System.out.println("休眠5秒");
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        printTime = new SimpleDateFormat("yy-MM-dd HH-mm-ss").format(new Date());
        System.out.println("任务1结束时间:" + printTime);
    }
}

再次运行,执行结果如下:

任务1开始时间:24-03-07 22-22-54
休眠5秒
任务1结束时间:24-03-07 22-22-59
任务2开始时间:24-03-07 22-22-59
任务2结束时间:24-03-07 22-22-59

关键:能够看到,任务1 运行5秒后才结束,导致 任务2 无法立即执行,而是延迟 5秒 才执行

验证缺点2

Task1

该类继承 TimerTask 类,实现 run() 方法,该方法中打印 开始时间 和 结束时间,中间由于 1/0 抛出 运行时异常

package timerstd2;

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.TimerTask;

public class Task1 extends TimerTask {

    @Override
    public void run() {
        String printTime = new SimpleDateFormat("yy-MM-dd HH-mm-ss").format(new Date());
        System.out.println("任务1开始时间:" + printTime);

        int i = 1/0;
        printTime = new SimpleDateFormat("yy-MM-dd HH-mm-ss").format(new Date());
        System.out.println("任务1结束时间:" + printTime);
    }
}

Task2

该类与 Task1 类几乎一样,只是没有中间 抛异常 的部分

package timerstd2;

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.TimerTask;

public class Task2 extends TimerTask {

    @Override
    public void run() {
        String printTime = new SimpleDateFormat("yy-MM-dd HH-mm-ss").format(new Date());
        System.out.println("任务2开始时间:" + printTime);

        printTime = new SimpleDateFormat("yy-MM-dd HH-mm-ss").format(new Date());
        System.out.println("任务2结束时间:" + printTime);
    }
}

主启动类

创建一个 Timer 类对象,并立即启动 Task1 和 Task2 任务

注意: 必须是 同一个Timer 类对象,上面介绍缺点时有提到

package timerstd;

import java.util.Timer;
import java.util.TimerTask;

public class TimerTest {

    public static void main(String[] args) {
        Timer timer = new Timer();

        TimerTask tt1 = new Task1();
        TimerTask tt2 = new Task2();

        // 立即执行
        timer.schedule(tt1,0);
        timer.schedule(tt2,0);
    }
}

执行结果:

任务1开始时间:24-03-07 23-51-18
Exception in thread "Timer-0" java.lang.ArithmeticException: / by zero
    at timerstd2.Task1.run(Task1.java:14)
    at java.util.TimerThread.mainLoop(Timer.java:555)
    at java.util.TimerThread.run(Timer.java:505)

由于 Task1 抛异常,导致其他任务都无法执行

解决

将 run() 方法中的代码都用 try catch 处理

Task1
package timerstd2.chuli;

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.TimerTask;

public class Task1 extends TimerTask {

    @Override
    public void run() {
        try {
            String printTime = new SimpleDateFormat("yy-MM-dd HH-mm-ss").format(new Date());
            System.out.println("任务1开始时间:" + printTime);

            int i = 1 / 0;
            printTime = new SimpleDateFormat("yy-MM-dd HH-mm-ss").format(new Date());
            System.out.println("任务1结束时间:" + printTime);
        }catch (Exception e){
            e.printStackTrace();
        }
    }
}
Task2
package timerstd2.chuli;

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.TimerTask;

public class Task2 extends TimerTask {

    @Override
    public void run() {
        try {
            String printTime = new SimpleDateFormat("yy-MM-dd HH-mm-ss").format(new Date());
            System.out.println("任务2开始时间:" + printTime);

            printTime = new SimpleDateFormat("yy-MM-dd HH-mm-ss").format(new Date());
            System.out.println("任务2结束时间:" + printTime);
        }catch (Exception e){
            e.printStackTrace();
        }
    }
}

再次执行,结果如下:

任务1开始时间:24-03-07 23-55-15
任务2开始时间:24-03-07 23-55-15
任务2结束时间:24-03-07 23-55-15
java.lang.ArithmeticException: / by zero
    at timerstd2.chuli.Task1.run(Task1.java:15)
    at java.util.TimerThread.mainLoop(Timer.java:555)
    at java.util.TimerThread.run(Timer.java:505)

Task1 执行报错,但 Task2 仍然能正常运行