深入浅出Java线程池:源码篇

在上一篇文章深入浅出Java线程池:理论篇中,已经介绍了什么是线程池以及基本的使用。(本来写作的思路是使用篇,但经网友建议后,感觉改为理论篇会更加合适)。本文则深入线程池的源码,主要是介绍ThreadPoolExecutor内部的源码是如何实现的,对ThreadPoolExecutor有一个更加清晰的认识。

ThreadPoolExecutor的源码相对而言比较好理解,没有特别难以读懂的地方。相信没有阅读源码习惯的读者,跟着本文,也可以很轻松地读懂ThreadPoolExecutor的核心源码逻辑。

本文源码jdk版本为8,该类版本为jdk1.5,也就是在1.5之后,ThreadPoolExecutor的源码没有做修改。

线程池家族

Java中的线程池继承结构如下图:(类图中只写了部分方法且省略参数)

深入浅出Java线程池:源码篇

顶层接口Executor表示一个执行器,他只有一个接口:execute() ,表示可以执行任务

ExecutorService在Executor的基础上拓展了更多的执行方法,如submit() shutdown() 等等,表示一个任务执行服务。

AbstarctExecutorService是一个抽象类,他实现了ExecutorService的部分核心方法,如submit等

ThreadPoolExecutor是最核心的类,也就是线程池,他继承了抽象类AbstarctExecutorService

此外还有ScheduledExecutorService接口,他表示一个可以按照指定时间或周期执行的执行器服务,内部定义了如schedule() 等方法来执行任务

ScheduledThreadPoolExecutor实现了ScheduledExecutorService接口,同时继承于ThreadPoolExecutor,内部的线程池相关逻辑使用自ThreadPoolExecutor,在此基础上拓展了延迟、周期执行等功能特性

ScheduledThreadPoolExecutor相对来说用的是比较少。延时任务在我们Android中有更加熟悉的方案:Handler;而周期任务则用的非常少。现在android的后台限制非常严格,基本上一退出应用,应用进程很容易被系统干掉。当然ScheduledThreadPoolExecutor也不是完全没有用处,例如桌面小部件需要设置定时刷新,那么他就可以派上用场了。

因此,我们本文的源码,主要针对ThreadPoolExecutor。在阅读源码之前,我们先来看一下ThreadPoolExecutor内部的结构以及关键角色。

内部结构

阅读源码前,我们先把ThreadPoolExecutor整个源码结构讲解一下,形成一个整体概念,再阅读源码就不会迷失在源码中了。先来看一下ThreadPoolExecutor的内部结构:

yGOUkq.md.png

ThreadPoolExecutor内部有三个关键的角色:阻塞队列、线程、以及RejectExecutionHandler(这里写个中文名纯粹因为不知道怎么翻译这个名字),他们的作用在理论篇有详细介绍,这里不再赘述。

在ThreadPoolExecutor中,一个线程对应一个worker对象,工人,非常形象。每个worker内部有一个独立的线程,他会不断去阻塞队列获取任务来执行,也就是调用阻塞队列的 poll 或者 take 方法,他们区别后面会讲。如果队列没有任务了,那么就会阻塞在这里。

workQueue,就是阻塞队列,当核心线程已满之后,任务就会被放置在这里等待被工人worker领取执行

RejectExecutionHandler本身是一个接口,ThreadPoolExecutor内部有这样的一个接口对象,当任务无法被执行会调用这个对象的方法。ThreadPoolExecutor提供了该接口的4种实现方案,我们可以直接拿来用,或者自己继承接口,实现自定义逻辑。在构造线程池的时候可以传入RejectExecutionHandler对象。

整个ThreadPoolExecutor中最核心的方法就是execute,他会根据具体的情况来选择不同的执行方案或者拒绝执行。

这样,我们就清楚ThreadPoolExecutor的内部结构了,然后,我们开始 Read the fucking code 吧。

源码分析 内部关键属性

ThreadPoolExecutor内部有很多的变量,他们包含的信息非常重要,先来了解一下。

ThreadPoolExecutor的状态和线程数整合在同一个int变量中,类似于view测量中MeasureSpec。他的高三位表示线程池的状态,低29位表示线程池中线程的数量,如下:

// AtomicInteger对象可以利用CAS实现线程安全的修改,其中包含了线程池状态和线程数量信息 private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0)); // COUNT_BITS=29,(对于int长度为32来说)表示线程数量的字节位数 private static final int COUNT_BITS = Integer.SIZE - 3; // 状态掩码,高三位是1,低29位全是0,可以通过 ctl&COUNT_MASK 运算来获取线程池状态 private static final int COUNT_MASK = (1 << COUNT_BITS) - 1;

线程池的状态一共有5个:

运行running:线程池创建之后即是运行状态

关闭shutdown:调用shutdown方法之后线程池处于shutdown状态,该状态会停止接收任何任务,阻塞队列中的任务执行完成之后会自动终止线程池

停止stop:调用shutdownNow方法之后线程池处于stop状态。和shutdown的区别是这个状态下的线程池不会去执行队列中剩下的任务

整理tidying:在线程池stop之后,进入tidying状态,然后执行 terminated() 方法,再进入terminated状态

终止terminated:线程池中没有任何线程在执行任务,线程池完全终止。

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

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