死磕 java线程系列之线程池深入解析——定时任务执行流程

threadpool_scheduletask

(手机横屏看源码更方便)

注:java源码分析部分如无特殊说明均基于 java8 版本。

注:本文基于ScheduledThreadPoolExecutor定时线程池类。

简介

前面我们一起学习了普通任务、未来任务的执行程,今天我们再来学习一种新的任务——定时任务。

定时任务是我们经常会用到的一种任务,它表示在未来某个时刻执行,或者未来按照某种规则重复执行的任务。

问题

(1)如何保证任务是在未来某个时刻才被执行?

(2)如何保证任务按照某种规则重复执行?

来个栗子

创建一个定时线程池,用它来跑四种不同的定时任务。

public class ThreadPoolTest03 { public static void main(String[] args) throws ExecutionException, InterruptedException { // 创建一个定时线程池 ScheduledThreadPoolExecutor scheduledThreadPoolExecutor = new ScheduledThreadPoolExecutor(5); System.out.println("start: " + System.currentTimeMillis()); // 执行一个无返回值任务,5秒后执行,只执行一次 scheduledThreadPoolExecutor.schedule(() -> { System.out.println("spring: " + System.currentTimeMillis()); }, 5, TimeUnit.SECONDS); // 执行一个有返回值任务,5秒后执行,只执行一次 ScheduledFuture<String> future = scheduledThreadPoolExecutor.schedule(() -> { System.out.println("inner summer: " + System.currentTimeMillis()); return "outer summer: "; }, 5, TimeUnit.SECONDS); // 获取返回值 System.out.println(future.get() + System.currentTimeMillis()); // 按固定频率执行一个任务,每2秒执行一次,1秒后执行 // 任务开始时的2秒后 scheduledThreadPoolExecutor.scheduleAtFixedRate(() -> { System.out.println("autumn: " + System.currentTimeMillis()); LockSupport.parkNanos(TimeUnit.SECONDS.toNanos(1)); }, 1, 2, TimeUnit.SECONDS); // 按固定延时执行一个任务,每延时2秒执行一次,1秒执行 // 任务结束时的2秒后,本文由公从号“彤哥读源码”原创 scheduledThreadPoolExecutor.scheduleWithFixedDelay(() -> { System.out.println("winter: " + System.currentTimeMillis()); LockSupport.parkNanos(TimeUnit.SECONDS.toNanos(1)); }, 1, 2, TimeUnit.SECONDS); } }

定时任务总体分为四种:

(1)未来执行一次的任务,无返回值;

(2)未来执行一次的任务,有返回值;

(3)未来按固定频率重复执行的任务;

(4)未来按固定延时重复执行的任务;

本文主要以第三种为例进行源码解析

scheduleAtFixedRate()方法

提交一个按固定频率执行的任务。

public ScheduledFuture<?> scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit) { // 参数判断 if (command == null || unit == null) throw new NullPointerException(); if (period <= 0) throw new IllegalArgumentException(); // 将普通任务装饰成ScheduledFutureTask ScheduledFutureTask<Void> sft = new ScheduledFutureTask<Void>(command, null, triggerTime(initialDelay, unit), unit.toNanos(period)); // 钩子方法,给子类用来替换装饰task,这里认为t==sft RunnableScheduledFuture<Void> t = decorateTask(command, sft); sft.outerTask = t; // 延时执行 delayedExecute(t); return t; }

可以看到,这里的处理跟未来任务类似,都是装饰成另一个任务,再拿去执行,不同的是这里交给了delayedExecute()方法去执行,这个方法是干嘛的呢?

delayedExecute()方法

延时执行。

private void delayedExecute(RunnableScheduledFuture<?> task) { // 如果线程池关闭了,执行拒绝策略 if (isShutdown()) reject(task); else { // 先把任务扔到队列中去 super.getQueue().add(task); // 再次检查线程池状态 if (isShutdown() && !canRunInCurrentRunState(task.isPeriodic()) && remove(task)) task.cancel(false); else // 保证有足够有线程执行任务 ensurePrestart(); } } void ensurePrestart() { int wc = workerCountOf(ctl.get()); // 创建工作线程 // 注意,这里没有传入firstTask参数,因为上面先把任务扔到队列中去了 // 另外,没用上maxPoolSize参数,所以最大线程数量在定时线程池中实际是没有用的 if (wc < corePoolSize) addWorker(null, true); else if (wc == 0) addWorker(null, false); }

到这里就结束了?!

实际上,这里只是控制任务能不能被执行,真正执行任务的地方在任务的run()方法中。

还记得上面的任务被装饰成了ScheduledFutureTask类的实例吗?所以,我们只要看ScheduledFutureTask的run()方法就可以了。

ScheduledFutureTask类的run()方法

定时任务执行的地方。

public void run() { // 是否重复执行 boolean periodic = isPeriodic(); // 线程池状态判断 if (!canRunInCurrentRunState(periodic)) cancel(false); // 一次性任务,直接调用父类的run()方法,这个父类实际上是FutureTask // 这里我们不再讲解,有兴趣的同学看看上一章的内容 else if (!periodic) ScheduledFutureTask.super.run(); // 重复性任务,先调用父类的runAndReset()方法,这个父类也是FutureTask // 本文主要分析下面的部分 else if (ScheduledFutureTask.super.runAndReset()) { // 设置下次执行的时间 setNextRunTime(); // 重复执行,本文由公从号“彤哥读源码”原创 reExecutePeriodic(outerTask); } }

可以看到,对于重复性任务,先调用FutureTask的runAndReset()方法,再设置下次执行的时间,最后再调用reExecutePeriodic()方法。

FutureTask的runAndReset()方法与run()方法类似,只是其任务运行完毕后不会把状态修改为NORMAL,有兴趣的同学点进源码看看。

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/wpwgyd.html