什么是线程池
线程池是一种池化技术,它预先创建一组线程,用于执行异步任务。当有新任务到来时,线程池可以立即分配一个线程来处理,而不需要临时创建。这样可以减少因为频繁创建和销毁线程而导致的开销。
线程池的应用场景
- 高并发服务器:如Web服务器、数据库服务器等,需要处理大量短生命周期的任务。
- 定时任务执行:如定时清理缓存、定期检查系统健康等。
- 异步任务处理:如文件上传下载、网络请求等IO密集型操作。
使用线程池的优势
- 降低资源消耗:线程池通过重用已创建的线程,减少了创建和销毁线程的资源消耗。
- 提高响应速度:由于线程已经预先创建好,任务到达时可以立即执行,无需等待线程创建,从而提高了系统的响应速度。
- 提高线程的可管理性:线程池提供了对线程数量和任务数量的管理能力,使得系统更加稳定和可控。
- 控制并发度:线程池可以有效地控制系统的并发度,防止因线程数量过多而导致系统资源耗尽。
线程池缺点
- 增加复杂性:线程池的使用增加了系统的复杂性,需要合理配置和管理才能发挥其优势。
- 可能导致资源不足:如果线程池的大小固定且任务量激增,可能会导致线程池中的线程不足以处理所有任务,从而影响性能。
- 调优难度:正确的线程池大小取决于许多因素,如任务的性质、系统负载等,错误的配置可能导致性能问题。
核心参数
线程池的七大参数包括核心线程数、最大线程数、空闲线程存活时间、时间单位、任务队列、线程工厂和拒绝策略。具体如下:
- 核心线程数(corePoolSize):这是线程池启动时创建的线程数量,也是线程池中最小的线程数量。即使这些线程处于空闲状态,线程池也会保持它们的存活,不会被销毁。
- 最大线程数(maximumPoolSize):这是线程池中允许存在的最大线程数量。当任务数量超过核心线程数时,线程池会逐步增加线程数量,直到达到这个上限。
- 空闲线程存活时间(keepAliveTime):当线程池中的线程数量大于核心线程数且线程处于空闲状态,那么在指定时间后,这个空闲线程将会被销毁,从而逐渐恢复到稳定的核心线程数数量。
- 时间单位(unit):与空闲线程存活时间一起使用,用于指定时间的单位,如秒、分钟等。
- 任务队列(workQueue):用于存放待执行的任务的队列,有不同的实现,如ArrayBlockingQueue、LinkedBlockingQueue等。
- 线程工厂(threadFactory):用于创建新线程的工厂类,可以自定义线程的创建方式,如设置线程名称、是否为后台线程等。
- 拒绝策略(handler):当任务队列已满且无法创建新线程时,线程池会采取的策略,常见的有AbortPolicy(抛出异常)、CallerRunsPolicy(调用者运行)和DiscardOldestPolicy(丢弃最老任务)等。
(图源网络)
线程池配置详见文章
CPU 密集型 和 IO密集型 的区别,如何确定线程池大小?-CSDN博客
四种常见的线程池详解文章
拒绝策略
线程池的拒绝策略主要有四种,分别是AbortPolicy、DiscardPolicy、DiscardOldestPolicy和CallerRunsPolicy。
当线程池中的任务队列已满,且线程数量达到最大线程数(maximumPoolSize)时,线程池无法接受新的任务,此时就会根据配置的拒绝策略来处理新提交的任务。具体如下:
- AbortPolicy(默认策略):丢弃任务并抛出RejectedExecutionException异常。这种策略会立即提醒调用者任务被拒绝,通常用于需要快速响应的场景。
- DiscardPolicy:静默丢弃任务,不抛出异常。适用于那些不重要或是可重复提交的任务。
- DiscardOldestPolicy:丢弃队列中最老的那个任务,然后尝试重新提交当前被拒绝的任务。这种策略不会抛弃当前提交的任务。
- CallerRunsPolicy:由提交任务的线程自己执行该任务。这种策略适用于执行器已经饱和,但提交的任务具有高优先级或需要即时执行的情况。
在实际应用中,选择合适的拒绝策略取决于具体场景和业务需求。例如,如果任务丢失会导致严重后果,那么使用AbortPolicy可能更为合适,因为它会明确告知任务无法执行。相反,如果任务不那么关键,DiscardPolicy可能是一个不错的选择,因为它可以避免程序因异常而中断。
此外,了解线程池的核心参数如核心线程数(corePoolSize)、阻塞队列(workQueue)和最大线程数(maximumPoolSize)对于合理配置线程池和选择拒绝策略也是非常重要的。通过这些参数的合理设置,可以在一定程度上避免触发拒绝策略,例如增加队列容量或者调整线程池大小。