# 《手写线程池》线程池核心技术-第06节:定时任务线程池核心技术解析

作者:冰河
星球:http://m6z.cn/6aeFbs (opens new window)
博客:https://binghe.gitcode.host (opens new window)
文章汇总:https://binghe.gitcode.host/md/all/all.html (opens new window)
源码获取地址:https://t.zsxq.com/0dhvFs5oR (opens new window)

沉淀,成长,突破,帮助他人,成就自我。

  • 本章难度:★★★☆☆
  • 本章重点:简单介绍定时任务线程池的核心执行流程,重点掌握ScheduledThreadPoolExecutor与Timer的区别、定时任务线程池的初始化、调度流程和优雅关闭流程。从全局视角掌握线程池的核心技术原理,学会融汇贯通,将线程池的编程思想灵活应用到自身实际项目中,提升实际项目的并发处理能力,以及自身的并发编程内功功底。

大家好,我是冰河~~

Java从JDK1.5版本开始提供了定时任务线程池,定时任务线程池的核心类就是ScheduledThreadPoolExecutor,从类结构上看,ScheduledThreadPoolExecutor类继承自ThreadPoolExecutor类,主要提供了定时任务线程池的功能,能够实现周期性的调度任务。

# 一、前言

上一节中,主要从源码角度深入分析了线程池优雅关闭的核心流程,重点对比分析了线程池中的shutdown()方法、shutdownNow()方法和awaitTermination()方法,以此深入解析线程池的优雅关闭流程。接下来,深入解析和探讨定时任务线程池。

# 二、本节诉求

简单介绍定时任务线程池的核心执行流程,重点掌握ScheduledThreadPoolExecutor与Timer的区别、定时任务线程池的初始化、调度流程和优雅关闭流程。从全局视角掌握线程池的核心技术原理,学会融汇贯通,将线程池的编程思想灵活应用到自身实际项目中,提升实际项目的并发处理能力,以及自身的并发编程内功功底。

# 三、ScheduledThreadPoolExecutor与Timer的区别

ScheduledThreadPoolExecutor类是从JDK1.5版本开始提供的定时任务线程池,而在JDK1.5版本之前实现定时任务主要使用的是Timer类和TimerTask类。接下来,就简单对比下Timer类与ScheduledThreadPoolExecutor类的区别。

# 3.1 线程实现的区别

在线程的实现方面,Timer类与ScheduledThreadPoolExecutor类是存在区别的。

(1)Timer类内部的实现是单线程模式,如果某个TimerTask任务的执行时间比较久,或者抛出了异常,会影响到其他任务的执行。

(2)ScheduledThreadPoolExecutor内部的实现是多线程模式,并且在线程池中线程是可以复用的,某个ScheduledFutureTask任务执行的时间比较久,不会影响到其他任务的调度执行。

# 3.2 系统时间区别

在获取系统时间层面,Timer类与ScheduledThreadPoolExecutor类对于时间的敏感程度不同。

(1)Timer内部是基于操作系统的绝对时间实现的,对于操作系统的时间绝对敏感,如果操作系统的时间发生变化,则Timer的线程调度不再准确。

(2)ScheduledThreadPoolExecutor类内部是基于相对时间实现的,操作系统时间的变化不会影响ScheduledThreadPoolExecutor对于线程的调度结果。

# 3.3 处理异常区别

Timer类与ScheduledThreadPoolExecutor类对于异常的处理方面存在区别。

(1)Timer类不会捕获异常,又因为Timer类内部是基于单线程实现的。所以,如果某个任务抛出异常,则其他任务也会受到影响不再执行。

(2)ScheduledThreadPoolExecutor类内部基于线程池调度任务,如果某个任务抛出异常后,其他任务仍会正常执行。

# 3.4 任务编排区别

Timer类与ScheduledThreadPoolExecutor类对于任务的排序方面存在区别。

(1)Timer类内部不支持对于任务的排序。

(2)ScheduledThreadPoolExecutor类内部支持对于任务的排序功能。在ScheduledThreadPoolExecutor类的内部,定义了一个静态内部类DelayedWorkQueue,DelayedWorkQueue本质上是一个有序队列,支持对存储在DelayedWorkQueue队列中的任务按照距离下次执行时间间隔的大小进行排序。

# 3.5 任务优先级区别

Timer类与ScheduledThreadPoolExecutor类在任务执行的优先级上存在区别。

(1)Timer中执行的TimerTask任务没有优先级的概念,只是根据系统的绝对时间来触发任务的执行。

(2)ScheduledThreadPoolExecutor中执行的ScheduledFutureTask有优先级的概念。ScheduledFutureTask类实现了java.lang.Comparable接口和java.util.concurrent.Delayed接口。也就是说,ScheduledFutureTask类实现了java.lang.Comparable接口的compareTo()方法和java.util.concurrent.Delayed接口的getDelay()方法,可以根据这两个方法实现任务的优先级。

# 3.6 返回结果区别

Timer类与ScheduledThreadPoolExecutor类在返回结果方面存在区别。

(1)Timer中执行的TimerTask类只是实现了java.lang.Runnable接口,无法获取结果数据。

(2)ScheduledThreadPoolExecutor中执行的ScheduledFutureTask类继承了FutureTask,FutureTask类实现了Future接口,可以通过Future接口的get()方法获取返回的结果数据。

# 四、定时任务线程池的初始化

从类结构的角度来看,ScheduledThreadPoolExecutor类是ThreadPoolExecutor类的子类,主要在ThreadPoolExecutor类实现的线程池基础上实现了定时任务的功能。ScheduledThreadPoolExecutor类的初始化主要是通过构造方法实现的,ScheduledThreadPoolExecutor类的构造方法如下所示。

public ScheduledThreadPoolExecutor(int corePoolSize) {
	super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS, new DelayedWorkQueue());
}
public ScheduledThreadPoolExecutor(int corePoolSize, ThreadFactory threadFactory) {
	super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
		  new DelayedWorkQueue(), threadFactory);
}
public ScheduledThreadPoolExecutor(int corePoolSize, RejectedExecutionHandler handler) {
	super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
		  new DelayedWorkQueue(), handler);
}
public ScheduledThreadPoolExecutor(int corePoolSize, ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
	super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
		  new DelayedWorkQueue(), threadFactory, handler);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

从ScheduledThreadPoolExecutor类的构造方法的源码可以看出,ScheduledThreadPoolExecutor的构造方法本质上还是调用的ThreadPoolExecutor类的构造方法。

# 五、定时任务线程池的调度流程

从源码角度来看,ScheduledThreadPoolExecutor类中经过一系列方法的调用,最终实现了ScheduledThreadPoolExecutor类的调度流程。接下来,就对ScheduledThreadPoolExecutor类涉及到调度流程的核心方法进行简单的介绍。

# 5.1 schedule()方法解析

schedule()方法实现了延时执行一次任务的功能,在ScheduledThreadPoolExecutor类中,提供了两个schedule()方法,源码如下所示。

public ScheduledFuture<?> schedule(Runnable command, long delay, TimeUnit unit) {
	//如果传递的Runnable对象和TimeUnit时间单位为空,则直接抛出空指针异常
	if (command == null || unit == null)
		throw new NullPointerException();
	//封装任务对象,在decorateTask方法中直接返回ScheduledFutureTask对象
	RunnableScheduledFuture<?> t = decorateTask(command, 
	     new ScheduledFutureTask<Void>(command, null, triggerTime(delay, unit)));
	//执行延时任务
	delayedExecute(t);
	//返回任务
	return t;
}
public <V> ScheduledFuture<V> schedule(Callable<V> callable, long delay, TimeUnit unit) 
	//如果传递的Callable对象和TimeUnit时间单位为空,抛出空指针异常
	if (callable == null || unit == null)
		throw new NullPointerException();
	//封装任务对象,在decorateTask方法中直接返回ScheduledFutureTask对象
	RunnableScheduledFuture<V> t = decorateTask(callable,
		new ScheduledFutureTask<V>(callable, triggerTime(delay, unit)));
	//执行延时任务
	delayedExecute(t);
	//返回任务
	return t;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

通过源码可以看出,ScheduledThreadPoolExecutor类中提供了两个重载的schedule()方法。两个schedule()方法中,只是第一个参数不同,一个方法是传递Runnable接口对象,另一个方法是传递Callable接口对象。

在schedule()方法的内部,会将传递进来的Runnable接口对象和Callable接口对象封装成RunnableScheduledFuture对象。而RunnableScheduledFuture对象本质上就是ScheduledFutureTask对象。将封装成的RunnableScheduledFuture对象,传入delayedExecute()方法中执行定时任务。

# 查看完整文章

加入冰河技术 (opens new window)知识星球,解锁完整技术文章、小册、视频与完整代码