前言

线程的创建和销毁太耗资源了,甚至比线程的执行时间还要长。那么如果程序一开始就创建好一个线程池,池中存放创建好的线程,用的时候从池中取,用完后返还,这样效率就会高很多。线程池属于开发中常见的一种池化技术,这类的池化技术的目的就是为了提高资源的利用率和效率,类似的池化技术还有HTTP连接池、数据库连接池等。本文就重点讲解一下线程池的原理和使用。

正文

一、ThreadPoolExecutor类的继承体系

首先来看JDK1.5为我们提供的线程池的核心实现类ThreadPoolExecutor,下图是该类的继承关系。

Java中线程中套用同一个线程有什么影响_java多线程


ThreadPoolExecutor实现的顶层接口是Executor,该接口提供了一个方法void execute(Runnable command);这个方法将任务的提交执行进行了解耦。用户无需关注线程是如何执行任务的,任务执行的逻辑完全交给执行器Executor,Executor完成线程的调度和任务的执行。

void execute(Runnable command);

第二层接口ExecutorService为线程池增加了一些能力,1、提供了管理线程池的方法。2、扩展了执行任务的能力,补充为一个或一批异步任务生成Future结果的方法。

Future<?> submit(Runnable task);

二、ThreadPoolExecutor是如何运行的?

Java中线程中套用同一个线程有什么影响_拒绝_02


ThreadPoolExecutor内部运行机制如上图所示,线程池内部实际上建造了一个生产者消费者模型,并且将线程管理任务管理分开,实现解耦。任务管理部分充当生产者的角色,当有任务提交,线程池首先会判断该任务后续的流转:1、直接申请线程执行该任务;2、缓冲到队列中等待线程执行;3、拒绝该任务。线程管理部分是消费者,线程被统一维护在池中,根据请求进行线程的分配,当线程执行完任务后,会继续获取新的任务来执行,最终当线程获取不到任务时,就会被回收。

三、线程池的运行机制

3.1 线程池生命周期管理

线程池运行的状态,并不是由用户显示设置的,而是随着线程池的运行,由内部来维护。线程池内部使用一个变量来维护两个值:运行状态线程数量。具体实现中,代码将这2个参数放在一起进行维护,其中高3位维护运行状态,低29位保存线程数量,两个状态之间互不干扰,如下图所示。用一个变量去存储2个值,可避免在做决策时,出现不一致的情况,不必为了维护两者的一致而申请锁资源。通过阅读线程池源代码也可以发现,经常会出现同时判断线程池运行状态和线程数量的情况。线程池也提供了若干方法去供程序获取线程池当前的运行状态和线程个数。这些操作都是使用的位运算,相比于基本运算,速度也会快很多。

private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));

ThreadPoolExecutor的运行状态有5种:

运行状态

描述

RUNNING

运行态,能接收新提交的任务,并且也能处理阻塞队列中的任务

SHUTDOWN

关闭状态,不再接收新提交的任务,但能继续处理阻塞队列中已保存的任务

STOP

不再接收新任务,也不处理阻塞队列中的任务,同时会中断正在处理任务的线程

TIDYING

所有的任务都已经终止了,workerCount(有效线程数)为0

TERMINATED

线程池彻底终止,在terminated()方法执行之后

Java中线程中套用同一个线程有什么影响_java多线程_03

3.2 任务执行机制

任务的调度是线程池的入口,当用户提交了一个任务,接下来这个新任务该如何执行都是由这个阶段来决定的。这部分是线程池的核心运行机制。

首先,所有任务的调度都是由execute()方法完成的,该方法完成的工作是:检查目前线程池的运行状态,运行线程数,运行策略,决定接下来执行的流程:是直接申请线程来执行,或是缓存到阻塞队列,还是直接拒绝该任务。执行过程如下:

1、首先检查线程池的运行状态,如果不是RUNNING,则直接拒绝,线程池要保证在RUNNING状态下才能执行任务。

2、如果workerCount < corePoolSize,则创建并启动一个线程来执行新提交的任务。

3、如果workerCount >= corePoolSize,且线程池内的阻塞队列未满,则将任务添加到该阻塞队列中。

4、如果workerCount >= corePoolSize && workerCount < maximumPoolSize,且阻塞队列已满,则创建并启动一个线程来执行新提交的任务。

5、如果workerCount >= maximumPoolSize,并且阻塞队列已满,则根据拒绝策略来处理该任务,默认的处理方式是直接抛异常。

Java中线程中套用同一个线程有什么影响_java多线程_04