huanzhiyazi / articles

我的原创文章,包括且不限于技术 blog,历史,文学写作,日常心得等
103 stars 32 forks source link

技术 / Java / Java线程池核心类ThreadPoolExecutor #5

Open huanzhiyazi opened 4 years ago

huanzhiyazi commented 4 years ago

目录



1 Java 线程池家族[Top]

Java 线程池主要有三类:ThreadPoolExecutorScheduledThreadPoolExecutorForkJoinPool。 其中 ScheduledThreadPoolExecutorThreadPoolExecutor 的基础上组合了线程调度功能(按照延迟时间排队进行延迟调度,周期调度)。所以总的来说只有两大类线程池:ThreadPoolExecutorForkJoinPool

ForkJoinPool 用的不多,以后可能会去研究一下,这里主要说一下 ThreadPoolExecutor的原理。



2 ThreadPoolExecutor 的线程池模型[Top]

ThreadPoolExecutor 是一个基于生产者-消费者模型的结构,如下图所示:

Thread Pool Structure

我们具体说明一下:



3 线程池的关闭[Top]

如果确定线程池不再需要运行了,可以手动关闭线程池——shutdown()。关闭线程池主要执行以下两步:

  1. 将线程池的状态设定为关闭状态——SHUTDOWN。
  2. 将工作池中还未运行结束的线程进行中断处理——interrupt,线程中的任务可以根据中断状态各自处理任务中断,避免无效执行。

第一步是关键,因为在工作池尝试清理阶段,如果线程池的状态已经是 SHUTDOWN 了,各个 worker 将不会再产生新的线程,包括核心线程也不再维持,相当于使 allowCoreThreadTimeout 参数无效。

但是,如果任务队列中还有任务未完成,则工作池尝试清理过程不会执行,且暂时不会关心 SHUTDOWN 状态标记,这时候线程池将继续执行完任务队列中的任务,直到进入工作池尝试清理阶段,才会真正执行清理过程。这是一个优雅的关闭流程,因为这样的关闭相当于只是给线程池发送一个关闭通知,线程池可以获知接下来不会再有新任务产生了,可以放心执行完已经提交但还缓存的任务,执行完后就可以不用管了,工作池中的线程将会全部释放,包括核心线程。

如果我们认为缓存的任务也并不重要,我们确实需要线程池尽快结束运行并清理,那么也是可以的。ThreadPoolExecutor 还提供了一个粗暴关闭手段——shutdownNow(),给我立马结束运行!!

shutdownNow() 实际上之比 shutdown() 多执行了一个关键步骤——清空任务队列。这样线程池在执行完工作池中的任务后,会提前进入到 工作池尝试清理阶段,比 shutdown() 更快地结束运行。

总结一下,工作池在 RUNNING 状态中完成所有任务进入尝试清理阶段后,不会释放核心线程资源,除非将 allowCoreThreadTimeout 置为 true;若手动关闭线程池将进入 SHUTDOWN 状态,工作池中的所有线程最终会被释放,allowCoreThreadTimeout 标记对工作池中的线程资源不再有约束作用



4 关于ScheduledThreadPoolExecutor的一点说明[Top]

ScheduledThreadPoolExecutor 的核心仍然是 ThreadPoolExecutor,它实现调度功能的关键在于其独特的任务队列。与通常的 FIFO 链式任务队列不同,ScheduledThreadPoolExecutor 采用的是带时间参数的小顶堆作为任务队列。这样在工作池每次从任务队列中取下一个任务时,将按照小顶堆的工作方式获取最小延迟任务。

后续我们还将看到,因为采用的任务队列不同而实现的特定线程池——CachedThreadPool。我将单独写一篇关于 CachedThreadPool 原理的文章。