概念
对于多个任务,采用并发编程处理是一个提高处理效率的方式。在 Java 中,可以在需要的时候创建一个线程,实现也非常方便,但是有一个问题,如果并发线程的数量很多,并且每个线程都是执行一个时间很短的任务就结束了,这样频繁创建线程会大大降低系统整体运行的效率,因为频繁创建、销毁线程会带来额外的开销。
new Thread 的弊端
- 每次 new Thread 新建线程对象性能差。
- 线程缺乏统一管理,可能无限制的新建线程,线程之间互相竞争,很可能占用过多系统资源导致死机或 OOM。
- 缺乏更多功能,比如定时执行、定期执行、线程中断等。
什么是线程池?
Java 提供线程池来处理这个问题,每次创建一个线程,线程执行完任务后不直接销毁,而是放入线程池里,下次需要创建线程时,直接从线程池里取一个线程,通过减少频繁创建、销毁线程的开销来提高运行效率。
线程池(Thread Pool)是一种基于池化思想管理线程的工具,经常出现在多线程服务器中。
线程池的好处
- 重用已有的线程,减少对象创建、消亡的开销,性能佳。
- 可有效控制最大并发线程,提高线程资源的使用率,同时避免过多资源竞争,避免堵塞。
- 提供定时执行、定期执行、单线程、并发数控制等功能。
实现
Java 中线程池的核心实现类是 ThreadPoolExecutor,涉及到的抽象类和接口如下:
生命周期
线程池运行的状态,并不是用户显式设置的,而是伴随着线程池的运行,由内部来维护。线程池内部使用一个变量维护两个值:运行状态(runState)和线程数量 (workerCount)。在具体实现中,线程池将运行状态(runState)、线程数量 (workerCount)两个关键参数的维护放在了一起,如下代码所示:
1 | private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0)); |
ctl 这个 AtomicInteger 类型,是对线程池的运行状态和线程池中有效线程的数量进行控制的一个字段, 它同时包含两部分的信息:线程池的运行状态(runState)和线程池内有效线程的数量 (workerCount),高 3 位保存 runState,低 29 位保存 workerCount,两个变量之间互不干扰。用一个变量去存储两个值,可避免在做相关决策时,出现不一致的情况,不必为了维护两者的一致,而占用锁资源。通过阅读线程池源代码也可以发现,经常出现要同时判断线程池运行状态和线程数量的情况。线程池也提供了若干方法去供用户获得线程池当前的运行状态、线程个数。这里都使用的是位运算的方式,相比于基本运算,速度也会快很多。
关于内部封装的获取生命周期状态、获取线程池线程数量的计算方法如以下代码所示:
1 | private static int runStateOf(int c) { return c & ~CAPACITY; } //计算当前运行状态 |
ThreadPoolExecutor 五种运行状态,分别为:
运行状态 | 状态描述 |
---|---|
RUNNING | 能接受新提交的任务,并且也能处理阻塞队列中的任务 |
SHUTDOWN | 关闭状态,不再接受新提交的任务,但可以继续处理阻塞队列中已保存的任务 |
STOP | 不能接收新任务,也不处理队列中的任务,会中断正在处理任务的线程 |
TIDYING | 所有的任务都已经终止,workerCount(有效线程数)为 0 |
TERMINATED | terminated() 方法执行完后进入该状态 |
示意图
任务调度
所有的任务的调度都是有 execute() 方法完成的,执行该方法之后:检查现在线程池的运行状态、运行线程数、运行策略,决定接下来执行的流程,是直接申请线程执行,或是缓冲到队列中执行,亦或是直接拒绝该任务。
- 首先检查线程池运行状态,如果不是 RUNNING,直接拒绝,线程池要保证在 RUNNING 的状态下执行任务。
- 如果 workerCount < corePoolSize,则创建并启动一个线程来执行提交的新任务。
- 如果 workerCount >= corePoolSize,且阻塞队列未满,则将任务添加到阻塞队列。
- 如果 workerCount >= corePoolSize && workerCount < maximumPoolSize,且线程池内的阻塞队列已满,则创建一个新线程来执行新提交的任务。
- 如果 workerCount >= maximumPoolSize,并且线程池内的阻塞队列已满,则根据拒绝策略来处理该任务,默认的处理方式是直接抛异常。
示意图
ThreadPoolExecutor
该类提供 4 个构造方法,参数如下:
- corePoolSize 线程池中保留的线程数,即使它们处于空闲状态,除非设置了 allowCoreThreadTimeOut
- maximumPoolSize 线程池中允许的最大线程数
- keepAliveTime 当线程数大于核心数时,多余空闲线程在终止之前等待新任务的最长时间
- unit keepAliveTime 参数的时间单位
- workQueue 在执行任务之前,用于保存任务的队列,此队列仅 Runnable execute 方法提交的 Runnable 任务
- threadFactory 执行程序创建新线程时使用的工厂
- handler 由于达到线程边界和队列容量而阻止执行时使用的处理程序
Executors
该类提供四个方法创建线程池:
- newCachedThreadPool() 创建一个可缓冲的线程池,可根据需要灵活调整线程池的配置
- newFixedThreadPool() 创建一个固定长度的线程池,可控制最大并发数,超出的线程在队列中等待
- newScheduledThreadPool() 创建一个固定长度的线程池,支持定时任务和周期性任务
- newSingleThreadExecutor() 创建一个单线程的线程池,只会使用唯一的线程执行队列中的任务,保证所有任务按照指定顺序执行。