多线程基础系列-线程池
文章目录
- 多线程基础系列-线程池
- 1. 何为线程池?
- 2. 为什么要使用线程池?
- 3. 线程池的工作原理
- 3.1 线程池的几个关键参数
- 3.2 线程池的工作原理
- 4. Java中的线程池实现
- 4.1 Executor 框架
- 4.2 线程池的创建方式
- 4.3 线程池的选择
- 4.3 有必要提一提`ThreadPoolTaskExecutor`
多线程基础系列-线程池
多线程编程是提升程序的性能和响应速度的重要手段,是我们开发人员手中的一把利器。本文将深入探讨线程池的相关知识,包括其基本概念、工作原理、优势以及在Java中的实现方式,帮助读者更好地理解和运用线程池。
1. 何为线程池?
线程池是一种线程使用模式,它维护了一个可复用的线程集合。当有任务需要执行时,线程池会从池中取出一个空闲线程来执行该任务,任务执行完毕后,线程并不会立即销毁,而是返回线程池中等待下一次被复用。这种模式可以有效减少线程创建和销毁的开销,提高程序的执行效率。
换句话,以前我们执行任务需要手动创建线程,以及做一些复杂的线程管控,现在我们只需要从一个池中拿一个线程来执行我们的任务便可
2. 为什么要使用线程池?
- 减少开销:避免频繁创建和销毁线程带来的资源消耗。
- 提高响应速度:复用已有线程快速响应任务请求。
- 合理利用资源:根据系统资源和任务特性合理控制线程数量,防止资源耗尽或闲置。
- 便于管理监控:方便对线程进行统一管理,监控线程状态和任务执行情况,便于调优和排查问题。
3. 线程池的工作原理
3.1 线程池的几个关键参数
-
corePoolSize:核心线程数
线程池中常驻的核心线程数量。即使线程空闲,这部分线程也会保留在线程池中,不会被回收。只有在工作队列满并且当前线程数小于最大线程数时才会创建新的线程。 -
maximumPoolSize:最大线程数
线程池能容纳的最大线程数量。当活动线程达到这个数值后,新来的任务如果不能放入任务队列将被拒绝处理。 -
keepAliveTime:空闲线程存活时间
当线程池中的线程数量超过核心线程数时,多余的空闲线程在等待新任务最长时间达到keepAliveTime后,如果还没有新任务到来,那么这些线程将被终止以节省资源。 -
workQueue:任务队列
用于保存等待执行的任务的阻塞队列。不同的队列实现会影响线程池的行为,常见的有ArrayBlockingQueue(固定大小)、LinkedBlockingQueue(无界或有限界限)、SynchronousQueue(直接传递,没有容量)等。 -
handler:拒绝策略
当线程池和任务队列都达到饱和状态时(即无法再接受新任务),如何处理新提交的任务。JDK内置了几种拒绝策略:
AbortPolicy:默认策略,丢弃任务并抛出RejectedExecutionException异常。
CallerRunsPolicy:调用者运行策略,直接在调用者线程中执行被拒绝的任务。
DiscardPolicy:静默丢弃任务,不抛出任何异常。
DiscardOldestPolicy:丢弃队列中最旧的任务,并尝试重新提交当前任务。
3.2 线程池的工作原理
- 线程池初始化:根据corePoolSize初始化核心线程。
- 任务提交:当任务提交到线程池时,根据当前线程数判断:
- 若当前线程数小于corePoolSize,创建新的线程执行任务。
- 若当前线程数大于或等于corePoolSize,任务被加入workQueue队列。
- 任务处理:当有空闲线程时,从workQueue中取出任务执行。
- 线程扩展:若队列已满且当前线程数小于maximumPoolSize,创建新的线程处理任务。
- 线程回收:当线程空闲时间超过keepAliveTime,多余的线程会被回收,直到线程数不超过corePoolSize。
- 拒绝策略:若队列已满且当前线程数达到maximumPoolSize,则根据拒绝策略处理新任务。
4. Java中的线程池实现
4.1 Executor 框架
Java提供了java.util.concurrent
包来支持线程池的创建和管理。其中,ThreadPoolExecutor
类是线程池的核心实现类,它提供了丰富的配置选项和灵活的线程管理功能。
示例代码:
ThreadPoolExecutor executor = new ThreadPoolExecutor(
2, 4, 60, TimeUnit.SECONDS,
new LinkedBlockingQueue<>(10),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy()
);
4.2 线程池的创建方式
线程池的创建方法总共有 7 种,但总体来说可分为 2 类:
- 通过 ThreadPoolExecutor 创建的线程池
- 通过 Executors 创建的线程池
线程池的创建方式总共包含以下 7 种(其中 6 种是通过 Executors 创建的,1 种是通过 ThreadPoolExecutor 创建的):
Executors.newFixedThreadPool()
:创建一个固定大小的线程池,可控制并发的线程数,超出的线程会在队列中等待;Executors.newCachedThreadPool()
:创建一个可缓存的线程池,若线程数超过处理所需,缓存一段时间后会回收多余的线程,若线程数不够,则新建线程;Executors.newScheduledThreadPool()
:创建一个可以执行延迟任务的线程池;Executors.newSingleThreadExecutor()
:创建单个线程数的线程池,它可以保证先进先出的执行顺序;Executors.newSingleThreadScheduledExecutor()
:创建一个单线程的可以执行延迟任务的线程池;Executors.newWorkStealingPool()
:创建一个抢占式执行的线程池(任务执行顺序不确定)JDK 1.8 添加ThreadPoolExecutor
:最原始的创建线程池的方式,它包含了 7 个参数可供设置
单线程池的意义: 虽然 newSingleThreadExecutor
和 newSingleThreadScheduledExecutor
是单线程池,但提供了工作队列,生命周期管理,工作线程维护等功能。
4.3 线程池的选择
我们看看《阿里巴巴Java开发手册终极版》中给我们的建议:
【强制】线程池不允许使用 Executors 去创建,而是通过 ThreadPoolExecutor 的方式,这样
的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。
说明:Executors 返回的线程池对象的弊端如下:
1)FixedThreadPool 和 SingleThreadPool:
允许的请求队列长度为 Integer.MAX_VALUE,可能会堆积大量的请求,从而导致 OOM。
2)CachedThreadPool 和 ScheduledThreadPool:
允许的创建线程数量为 Integer.MAX_VALUE,可能会创建大量的线程,从而导致 OOM。
4.3 有必要提一提ThreadPoolTaskExecutor
ThreadPoolTaskExecutor 是 Spring 框架对 ThreadPoolExecutor 的封装,用于在 Spring 应用中更方便地管理线程池。它继承自 AbstractExecutorService,并提供了与 Spring 集成的便利性。
抱歉刚才的表格格式没有完全符合Markdown规范。以下是完全符合Markdown格式的表格:
markdown复制
特性 | ThreadPoolExecutor | ThreadPoolTaskExecutor |
---|---|---|
语言层面 | Java 标准库 | Spring 框架 |
配置方式 | 手动创建,直接使用构造方法 | 通过 Spring 配置类或 XML 配置 |
与 Spring 的集成 | 需要手动管理生命周期 | 自动管理生命周期,支持 Spring 生命周期管理 |
线程池参数 | 提供丰富的配置选项 | 提供类似的配置选项,更易与 Spring 集成 |
使用场景 | 适用于任何 Java 应用 | 专为 Spring 应用设计,适合与 @Async 和 @Scheduled 集成 |
底层实现 | 基于 java.util.concurrent | 基于 ThreadPoolExecutor 封装 |