Java中的定时任务

844 阅读5分钟

小知识,大挑战!本文正在参与“程序员必备小知识”创作活动

1. 线程等待实现

创建一个Thread,使用while循环运行,并通过sleep方法来实现定时效果。

简单直接,但是实现功能有限,循环延迟执行。

public class Task {
    public static void main(String[] args) {
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                while (true) {
                    System.out.println("Hello Java!");
                    try {
                        Thread.sleep(2000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        };
        Thread thread = new Thread(runnable);
        thread.start();
    }
}

2. JDK自带Timer实现

Timer是一种定时器工具,用来在一个后台线程根据计划执行指定任务,可以安排执行一次或者多次。

  • Timer API是JDK中较早出现的,在java.util.TimerTask包中
  • 使用时需要指定类继承TimerTask类并重写其中的run()`方法

2.1 Timer相关方法

Timer类时java.util包中用于执行定时任务的类,执行时使用队列存放需要执行的任务,并开启单独的线程来执行任务

// 在指定延迟时间后执行指定的任务
schedule(TimerTask task,long delay);

// 在指定时间执行指定的任务。(只执行一次)
schedule(TimerTask task, Date time);

// 延迟指定时间(delay)之后,开始以指定的间隔(period)重复执行指定的任务
schedule(TimerTask task,long delay,long period);

// 在指定的时间开始按照指定的间隔(period)重复执行指定的任务
schedule(TimerTask task, Date firstTime , long period);

// 在指定的时间开始进行周期性(period)的执行任务
scheduleAtFixedRate(TimerTask task,Date firstTime,long period);

// 在指定的延迟时间后开始进行周期性(period)的执行任务
scheduleAtFixedRate(TimerTask task,long delay,long period);

// 终止定时器任务,丢弃所有当前已安排的任务。
cancal();

// 从此定时器的任务队列中移除所有已取消的任务。
purge();

2.2 定义定时任务类

定时任务类需要继承java.util包中的TimerTask类,并实现其中的run()方法。

public class DoTask extends TimerTask {

    @Override
    public void run() {
        System.out.println("TimerTask执行-----:" + System.currentTimeMillis());
    }
}

2.3 实际使用示例

实际使用定时任务时,需要创建Timer类的对象,调用对象的相关方法并传入定时任务和任务触发机制。

 //类中定时main方法执行定时任务
 public static void main(String[] args){
     Timer timer = new Timer();
     timer.schedule(new DoTask(),1000,1000);
 }

2.4 Timer的问题

Timer可以执行定时任务、延时任务、周期执行任务等,但是Timer也存在一些问题:

  1. 在执行任务时是基于绝对时间而不是相对时间,因此可能会对系统时间过分依赖
  2. 在执行任务时如果出现未捕获的异常,则所有任务都会停止执行

在IDEA中使用TImer时会提示我们使用ScheduledExecutorService来代替Timer

image.png

3. ScheduledExecutorService

ScheduledExecutorService是Java1.5版本新增的定时任务接口,基于线程池的方式,每个任务会开启一个新的线程执行,互不影响。

ScheduledExecutorService存在于java.util.concurrent包中

针对使用Timer的提示问题,IDEA还为我们提供了新方案的使用方法:

//ScheduledExecutorService代替Timer吧  
//Inspection info: 处理定时任务时,Timer运行多个TimeTask时,只要其中之一没有捕获抛出的异常,其它任务便会自动终止运行,使用ScheduledExecutorService则没有这个问题。 
            
//org.apache.commons.lang3.concurrent.BasicThreadFactory
ScheduledExecutorService executorService = new ScheduledThreadPoolExecutor(1, new BasicThreadFactory.Builder().namingPattern("example-schedule-pool-%d").daemon(true).build());
executorService.scheduleAtFixedRate(new Runnable() {
    @Override
    public void run() {
        //do something
    }
},initialDelay,period, TimeUnit.HOURS);

3.1 提供方法

ScheduledExecutorService中提供了以下四个方法:

//创建一次定时任务,指定触发延迟时间,任务传入Runnable
ScheduledFuture<?> schedule(Runnable command,long delay, TimeUnit unit);

//创建一次定时任务,指定触发延迟时间,任务传入Callable带回调
<V> ScheduledFuture<V> schedule(Callable<V> callable,long delay, TimeUnit unit);

//创建周期执行定时任务,initialDelay是第一次执行延迟时长,period是两次成功执行间隔
//如果程序执行时间超过间隔时间,则会在上次执行结束后立即执行下次
ScheduledFuture<?> scheduleAtFixedRate(Runnable command,long initialDelay,long period,TimeUnitunit);

//创建周期执行任务,initialDelay第一次执行延迟时长,dalay上次结束和下次开始间隔时长
ScheduledFuture<?> scheduleWithFixedDelay(Runnable command,long initialDelay,long delay,TimeUnitunit);

ScheduledExecutorService作为一个接口,在创建其对象并调用方法时需要使用其实现类:

  • ScheduledThreadPoolExecutor类,用于创建ScheduledExecutorService类型对象
    • 因为其也继承了线程池创建类,创建时需要指定核心线程数量
  • DelegatedScheduledExecutorService类,是Executors类的内部类

3.2 任务创建

ScheduledExecutorService提供的方法中需要的任务是Runnable或者Callable<V>,而实际上java.util中的TimerTask类也是实现了Runnable接口的。

我们可以直接定义任务类来实现Runnable接口,并将任务类作为ScheduledExecutorService的参数传递执行。

public class DoTaskBySchedule implements Runnable {

    @Override
    public void run() {
        System.out.println("任务执行,当前时间:"+ System.currentTimeMillis());
    }
}

3.3 使用示例

使用时先创建ScheduledExecutorService类型的对象,并执行对象的方法。

//类中的main方法,执行定时任务
public static void main(String[] args){
    ScheduledExecutorService scheduledExecutorService = new ScheduledThreadPoolExecutor(5);
    scheduledExecutorService.scheduleAtFixedRate(new DoTaskBySchedule(),1000,2000, TimeUnit.MILLISECONDS);
}

3.4 存在不足

ScheduledExecutorService使用时仍然会存在一定的不足,如:

  • 没有可以根据时间指定的执行方法,需要传入延迟时间参数执行
  • 指定定时任务时会以上次执行完成为前提,上次没有执行完则不会执行下次任务

4. 总结

综上所述,Java的JDK中自带的一些定时器的使用方法对于简单的延迟任务使用还是可以的,但是对于比较复杂、需求精细、周期性定时任务的支持不太好,对于项目业务中使用的大量定时任务还是需要引入专门的框架来完成。