Java 线程池

线程池的原理

相信在开发的过程中,线程池或多或少都有使用过吧,但是为什么要使用线程池呢?诸如 Web 服务器、数据库服务器、文件服务器或邮件服务器之类的许多服务器应用程序都面向处理来自某些远程来源的大量短小的任务。请求以某种方式到达服务器,这种方式可能是通过网络协议(例如 HTTP、FTP )、通过 JMS队列或者可能通过轮询数据库。不管请求如何到达,服务器应用程序中经常出现的情况是:单个任务处理的时间很短而请求的数目却是巨大的。每当一个请求到达就创建一个新线程,然后在新线程中为请求服务,但是频繁的创建线程,销毁线程所带来的系统开销其实是非常大的。

线程池的情况

线程池为线程生命周期开销问题和资源不足问题提供了解决方案。通过对多个任务重用线程,线程创建的开销被分摊到了多个任务上。其好处是,因为在请求到达时线程已经存在,所以无意中也消除了线程创建所带来的延迟。这样,就可以立即为请求服务,使应用程序响应更快。而且,通过适当地调整线程池中的线程数目,也就是当请求的数目超过某个阈值时,就强制其它任何新到的请求一直等待,直到获得一个线程来处理为止,从而可以防止资源不足。

线程池的状态

线程池知识点总结 - 图1
从上图可以看出线程池有这五种状态

运行状态(RUNNING):

在运行的状态下,线程池可以接受新的任务,但也可以处理阻塞队列里面的任务。当执行shutdown方法可以进入待关闭的状态,执行showdownNow就进入停止状态。

待关闭状态(SHUTDOWN):

在待关闭的状态下,线程池就不会再接受新的任务了,它继续处理在阻塞队列里的任务。

停止状态(STOP):

在停止的状态下,线程池也不会接受新的任务,但它也不会处理阻塞队列中的任务,接着会开始尝试把执行中的任务结束掉。当工作的线程的数量为0的时候,它就进入整理状态去了

终止状态(TERMINATED):

在这个状态下,线程池是完全处于终止状态的,并且把所有的资源完成了释放。

线程池有什么重要的属性

线程池里有很多核心参数,每一个参数都有它的作用,各个参数组成一个完整的线程池工作。线程池里有六个参数是最为重要的:线程状态、工作线程数量、核心线程数、最大线程数、创建线程的工厂、缓存任务的阻塞队列、非核心线程存活时间和拒绝策略。

线程状态和工作线程数量

首先知道线程池它是有状态的,所以它在不同状态下,它的行为又是不一样的。接着就是需要使用线程来执行任务,所以在线程池的里面就封装了一个worker内部类来作为工作线程,每一个都维持着一个Thread。线程池就是要控制线程资源的合理使用。
这里的ctl是原子操作的变量,前面的3bit是表示线程池状态,后面的29位表示线程个数。
线程池知识点总结 - 图2

核心线程数与最大线程数

核心线程数:

表示线程池里核心线程的数量。

最大线程数:

用来表示线程池里最多可以创建的线程数量。
创建线程是需要代价的,所以不可以每一次执行任务都要创建一个线程吧?但任务多的时候,也不可以只有小量的线程在执行吧?所以只需要创建合适的线程来处理任务。随着任务数量的变化,线程也随之变化
线程池知识点总结 - 图3

创建线程工厂

线程工厂由ThreadFactory完成的。

缓存任务的阻塞队列

当工作的线程数达到了corepoolsize的时候,这个时候又有新任务,这个时候就会把任务放到阻塞队列中去进行等待。为什么不创建新的呢?有可能有线程已经要完成任务了,可以马上投入到新的任务中去,所以阻塞队列是缓冲机制,给核心的线程一个机会展示他们的能力。
线程池知识点总结 - 图4

非核心线程存活时间

核心线程跟创建的先后顺序没关系,但是它跟工作线程的个数是有关的,如果当前的工作线程个数是大于核心线程数的话,那么它的所有线程都有可能是非核心线程,也就是都有可能被回收掉。当一个线程执行完任务,就会去阻塞队列里找新的任务,那么在找到任务的过程当中,它就是一个闲置的线程。

拒绝策略

如果工作线程达到了最大线程数的时候,这时有新任务的话,线程池就会想执行但执行不了,因为没有空间来存放这个任务了,也没有办法创建新的线程了,这个时候拒绝策略就出现了。

工作流程

线程池知识点总结 - 图5

提交任务

当线程池提交任务的时候,会有三种情况:创建线程,加入阻塞队列,拒绝策略。所以提交任务的过程如下图所示。
线程池知识点总结 - 图6

创建工作线程

创建工作线程需要做一系列的判断:

  • 当线程池的状态是SHUTDOWN或者STOP时,就不能创建线程。
  • 当线程工厂创建线程失败时,也不能创建新的线程。
  • 拿当前工作线程的数量与核心线程数、最大线程数进行比较,如果前者大于后者的话,也不允许创建。

    启动工作线程

    当工作线程创建成功后,这时就需要启动该工作线程。Worker对象中关联着一个Thread,只要通过worker.thread.start()来启动该线程即可。

    获取任务并执行

    在runWorker方法被调用之后,就是执行具体的任务了,首先需要拿到一个可以执行的任务,而Worker对象中默认绑定了一个任务,如果该任务不为空的话,那么就是直接执行。执行完了之后,就会去阻塞队列中获取任务来执行。