这一节,我们说一说ExecutorService接口的另一个实现类——定时执行任务机制。

image.png

ScheduledExecutorService接口

ScheduledExecutorService为我们提供了定时执行任务的机制。

java.util.concurrent.ScheduledExecutorService接口继承了ExecutorService,它的最主要的功能就是可以对其中的任务进行调度,比如延迟执行、定时执行等等。

定时任务接口具体实现

ScheduledThreadPoolExecutor是ExecutorService的另一个实现类,从上面Java线程池ExecutorService继承树这幅图可以看出,ScheduledThreadPoolExecutor直接继承自ThreadPoolExecutor并实现接口ScheduledExecutorService。构造参数很简单,只有3个:

  1. int corePoolSize:线程池维护线程的最少数量
  2. ThreadFactory threadFactory:线程工程类,线程池用它来制造线程
  3. RejectedExecutionHandler handler:线程池对拒绝任务的处理策略

ScheduledThreadPoolExecutor 类的功能也主要体现在ScheduledExecutorService 接口上,而所以在介绍ScheduledThreadPoolExecutor之前先介绍一下ScheduledExecutorService接口。

具体使用方法请参考上文ThreadPoolExecutor或者使用Executors。

定时任务的创建

我们这样创建ScheduledExecutorService:

  1. ScheduledExecutorService executorService
  2. = Executors.newSingleThreadScheduledExecutor();

ScheduledExecutorService接口定义:

  1. public interface ScheduledExecutorService extends ExecutorService {
  2. public ScheduledFuture<?> schedule(Runnable command, long delay, TimeUnit unit);
  3. public ScheduledFuture<?> scheduleAtFixedRate(Runnable command,long initialDelay, long period, TimeUnit unit);
  4. public ScheduledFuture<?> scheduleWithFixedDelay(Runnable command,long initialDelay, long delay,TimeUnit unit);
  5. }

从上面接口定义我们知道,提供了四个方法,下面我们就分别介绍:

  1. schedule (Runnable task, long delay, TimeUnit timeunit)
  2. schedule (Callable task, long delay, TimeUnit timeunit)
  3. scheduleAtFixedRate (Runnable, long initialDelay, long period, TimeUnit timeunit)
  4. scheduleWithFixedDelay (Runnable, long initialDelay, long period, TimeUnit timeunit)

executorServiceschedule方法,可以传入Runnable也可以传入Callable:

  1. Future<String> future = executorService.schedule(() -> {
  2. // ...
  3. return "Hello world";
  4. }, 1, TimeUnit.SECONDS);
  5. ScheduledFuture<?> scheduledFuture = executorService.schedule(() -> {
  6. // ...
  7. }, 1, TimeUnit.SECONDS);

接下来我们具体介绍四种构造函数。

  1. schedule (Runnable task, long delay, TimeUnit timeunit)

这个方法的意思是在指定延迟之后运行task。这个方法有个问题,就是没有办法获知task的执行结果。如果我们想获得task的执行结果,我们可以传入一个Callable的实例(后面会介绍)。

  1. ScheduledExecutorService scheduledExecutorService =
  2. Executors.newScheduledThreadPool(5);
  3. ScheduledFuture scheduledFuture =
  4. scheduledExecutorService.schedule(new Callable() {
  5. public Object call() throws Exception {
  6. System.out.println("Executed!");
  7. return "Called!";
  8. }
  9. },5,TimeUnit.SECONDS);
  10. System.out.println("result = " + scheduledFuture.get());
  11. scheduledExecutorService.shutdown();
  1. schedule (Callable task, long delay, TimeUnit timeunit)

这个方法与schedule (Runnable task)类似,也是在指定延迟之后运行task,不过它接收的是一个Callable实例,此方法会返回一个ScheduleFuture对象,通过ScheduleFuture我们可以取消一个未执行的task,也可以获得这个task的执行结果。

ScheduledExecutorService scheduledExecutorService =
    Executors.newScheduledThreadPool(5);

ScheduledFuture scheduledFuture =
scheduledExecutorService.schedule(new Callable() {
    public Object call() throws Exception {
        System.out.println("Executed!");
        return "Called!";
    }
},
5,
TimeUnit.SECONDS);

System.out.println("result = " + scheduledFuture.get());
scheduledExecutorService.shutdown();

还有两个比较相近的方法:

scheduleAtFixedRate( Runnable command, long initialDelay, long period, TimeUnit unit )

scheduleWithFixedDelay( Runnable command, long initialDelay, long delay, TimeUnit unit )

两者的区别是前者的period是以任务开始时间来计算的,后者是以任务结束时间来计算。

  1. scheduleAtFixedRate (Runnable, long initialDelay, long period, TimeUnit timeunit)

这个方法的作用是周期性的调度task执行。task第一次执行的延迟根据initialDelay参数确定,以后每一次执行都间隔period时长。

如果task的执行时间大于定义的period,那么下一个线程将在当前线程完成之后再执行。整个调度保证不会出现一个以上任务同时执行。

  1. scheduleWithFixedDelay (Runnable, long initialDelay, long period, TimeUnit timeunit)

scheduleWithFixedDelay的参数和scheduleAtFixedRate参数完全一致,它们的不同之处在于对period调度周期的解释。

在scheduleAtFixedRate中,period指的两个任务开始执行的时间间隔,也就是当前任务的开始执行时间和下个任务的开始执行时间之间的间隔。

而在scheduleWithFixedDelay中,period指的当前任务的结束执行时间到下个任务的开始执行时间。

ScheduledExecutorService的关闭

和ExecutorService类似, 我们在使用完ScheduledExecutorService时需要关闭它。如果不关闭的话,JVM会一直运行直,即使所有线程已经关闭了。

关闭ScheduledExecutorService可以使用其继承自ExecutorService接口的shutdown()shutdownNow()方法。具体关于线程池的关闭,我们后面细说。