Java定时任务介绍


一、定时任务

java.util 包下提供了 TimerTimerTask 用于提交定时任务,其中 Timer 用于管理定时任务,TimerTask 创建定义定时任务的具体执行内容。

1. 定时任务

TimerTask 使用类似于线程,新建任务类 CustomTask 继承 TimerTask 类并重写 run() 方法,在 run() 方法中定义任务的具体执行逻辑。

如下示例中我定义了一个定时任务打印当前系统时间并自增计数器 num

class CustomTask extends TimerTask {

    private String taskName;

    public CustomTask(String taskName) {
        this.taskName = taskName;
    }

    @Override
    public void run() {
        try {
            DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd hh:mm:ss");
            String nowadays = formatter.format(LocalDateTime.now());
            System.out.printf("Schedule [%s] active, current time: %s%n", this.taskName, nowadays);
        } catch (Exception ex) {
            System.out.println("Error running thread " + ex.getMessage());
        }
        num.getAndIncrement();
    }
}

2. 任务提交

完成定时任务的创建之后即可通过 Timer 类的 schedule() 方法进行提交,Timer 中维护了一个默认大小为 128 的工作队列用于接收传入定时任务。

schedule(task, delay, period) 方法的三个入参描述参考下表。

方法 描述
task 需要指定的定时任务。
delay long 类型,任务首次触发时间与当前的时间差。
period long 类型,定时任务间隔周期。

如下示例中通过 Timer 提交了一个定时任务,并设置在 2 秒后触发,任务每隔 5 秒执行一次。

public class ScheduleTest {

    private volatile AtomicInteger num = new AtomicInteger(0);

    @Test
    public void timeTaskDemo() throws InterruptedException {
        Timer time = new Timer();
        long delay = TimeUnit.SECONDS.toMillis(2);
        long period = TimeUnit.SECONDS.toMillis(5);
        time.schedule(new CustomTask(), delay, period);

        while (true) {
            if (num.get() > 3) {
                time.cancel();
                System.out.println("Cancel time task.");
                break;
            }
        }
        // purge(): Remove the cancelled task from time queue.
        // If timer queue is empty that is eligible for gc
        time.purge();
    }
}

除了 schedule() 方法 Timer 类中还提供其它相应的操作,具体信息参考下表:

方法 描述
cancel() 清空排队中定时任务并不再接收新提交任务,但是正在执行的任务不会中断。
purge() 清空队列中已经被 cancel 的任务,当队列为空时即可被 GC。

二、定时线程池

1. 基本介绍

上述提到 Timer 类中维护了一个工作队列并以单线程执行定时任务,而 ScheduledExecutorService 可用于创建定时线程任务资源池。

ScheduledExecutorService 提交任务不再限制必须继承于 TimerTask,任务类继承于线程同样允许。

2. 任务提交

通过 newScheduledThreadPool() 创建定时任务线程池资源,通过构造器指定线程数。

方法 作用
schedule() 提交定时任务,仅执行一次。
scheduleAtFixedRate() 提交定时任务,任务触发间隔周期是按上次任务开始时间计算。
scheduleWithFixedDelay() 提交定时任务,任务触发间隔周期是按上次任务完成时间计算。

ScheduledExecutorService 线程池存在 scheduleAtFixedRate()scheduleWithFixedDelay() 两种方式提交任务,二者的区别如下:

  • scheduleAtFixedRate()
    间隔时间是从上一个任务开始时计算,无论上个任务是否已经结束。
  • scheduleWithFixedDelay()
    间隔时间是从上个任务完成时开始计算,只有当上个任务结束才会开始计时。

scheduleAtFixedRate 与 scheduleWithFixedDelay

public void scheduledPoolDemo() throws Exception {
    ScheduledExecutorService executor = Executors.newScheduledThreadPool(3);
    int start = 1;
    int interval = 3;
    // 当前时间 1 秒后执行一次
    executor.schedule(() -> {
        System.out.println("Task-1 running.");
    }, interval, TimeUnit.SECONDS);
    
    // 当前时间 1 秒后执行, 且每隔 3 秒重复执行
    // (间隔时间:上一次任务开始时计时)
    executor.scheduleAtFixedRate(new Task("fixed-rate"), start, interval, TimeUnit.SECONDS);

    // 当前时间 1 秒后执行, 上一任务执行完成 3 秒后重复执行
    // (间隔时间:上一次任务结束时计时)
    executor.scheduleWithFixedDelay(new Task("fixed-delay"), start, interval, TimeUnit.SECONDS);
    executor.shutdown();
}

文章作者: 烽火戏诸诸诸侯
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 烽火戏诸诸诸侯 !
  目录