Java线程池的工作机制详解
作者:高锰酸钾_
Java线程池的工作机制
线程池是一种多线程管理机制,用于限制和控制并发线程的数量,以提升系统性能和资源利用率,降低频繁创建和销毁线程的开销,线程池是 Java 并发编程中的重要工具之一,广泛应用于高性能、多线程的场景
线程池通过复用已创建的线程执行多个任务,避免线程的频繁创建和销毁,线程池可以限制线程数量,防止大量线程导致的系统资源耗尽,线程池使用队列管理任务,支持任务调度和优先级,线程池提供策略处理超出能力范围的任务
我们可以使用Java提供的ThreadPoolExecutor
类来创建一个线程池实例,让我们先来看一下他的构造方法:
ThreadPoolExecutor( int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler )
可以看到他的构造方法有七个参数
- corePoolSize:核心线程数,线程池中始终保持的线程数量
- maximumPoolSize:最大线程数,线程池中允许的最大线程数量
- keepAliveTime:空闲线程的存活时间,超过该时间的空闲线程将被回收
- unit:时间单位,是keepAliveTime的单位
- workQueue:任务队列,用于存储等待执行的任务
- threadFactory:线程工厂,用于创建新线程
- handler:拒绝策略,用于处理超出线程池能力范围的任务
那么为什么除了传入一个核心线程数之外,还要传入最大线程数呢?任务队列中又是哪些任务在排队等待呢?我们一起来探讨一下线程池的工作机制
假设创建一个核心线程为2,最大线程数为4的线程池:
ThreadPoolExecutor pool = new ThreadPoolExecutor( 2, // 核心线程数量 4, // 最大线程数量 60, // 空闲线程最大存活时间 TimeUnit.SECONDS, // 单位 new ArrayBlockingQueue<>(2), //创建任务队列 Executors.defaultThreadFactory(), // 创建线程工厂 new ThreadPoolExecutor.AbortPolicy() // 任务拒绝策略 );
核心线程
如果同一时刻来了两个任务:任务1和任务2,那么自然就会把两个任务交给两个核心线程去执行:
等待队列
如果同一时刻提交了4个新的任务,但是我们定义的线程池只有两个核心线程用来执行任务1和任务2,此时核心线程全部繁忙,新任务会被放入等待队列,那么任务3和任务4就会进入等待队列中等待,队列有容量限制,我们上面传入的阻塞队列容量是2:
临时线程
如果同一时刻提交的任务非常多,比如提交了6个任务,那么核心线程依旧会执行任务1和任务2,由于此时核心线程全部繁忙,新任务会被放入等待队列,任务3和任务4会被放如等待队列中等待执行
但是将任务5放入等待队列时,等待队列已满,线程池会尝试创建非核心线程:临时线程来执行新的任务,但是核心线程与临时线程的总和不得超过最大线程数量,也就是我们传入的4,所以此时线程池会创建2个临时线程来执行任务5和任务6
临时线程用于处理高峰期任务,但是临时线程并不是核心线程,当临时线程处于空闲状态超过 keepAliveTime
后,临时线程就会被销毁
任务拒绝
如果任务数量超过了线程池最大处理能力(核心线程 + 临时线程 + 等待队列),则执行拒绝策略,例如同一时间提交了7个新任务,按照上面的运行机制,任务1、任务2、任务5和任务6会被线程执行,任务3和任务4会在等待队列中等待空闲线程
但是由于核心线程 + 临时线程 + 等待队列全部满员,此时任务7就会执行拒绝策略,共有四种拒绝策略:
任务拒绝策略 | 说明 |
---|---|
ThreadPoolExecutor.AbortPolicy | 默认策略 丢弃任务并抛出RejectedExecutionException异常 |
ThreadPoolExecutor.DiscardPolicy | 丢弃任务,但是不抛出异常 |
ThreadPoolExecutor.DiscardOldestPolicy | 抛弃等待队列中等待时间最久的任务,把当前任务加入等待队列 |
ThreadPoolExecutor.CallerRunsPolicy | 调用任务的run()方法绕过线程池直接执行 |
这就是线程池的工作机制,线程池通过任务复用和资源管理提升了系统的并发性能,但使用时需结合具体场景合理配置参数,掌握线程池机制并深入理解其应用场景,将有效提升 Java 开发效率和系统稳定性
那么通常在我们的项目中,线程池的容量应该设置为多大呢?
线程容量
项目中的线程容量(即线程池的大小)设置需要基于项目的类型来制定,不同的项目设置不同的容量:
CPU密集型任务
对于主要依赖CPU计算的任务(如数据处理、图像渲染),线程数量建议接近或等于CPU核心数(包括逻辑核心)。
计算公式:
这样可以充分利用CPU,但又不会因线程上下文切换过多而导致性能下降。
I/O密集型任务
对于涉及大量I/O操作的任务(如文件读写、网络请求、数据库查询),线程池可以设置为CPU核心数的多倍,因为I/O操作通常会让线程处于阻塞状态。
计算公式:
更多线程可以隐藏I/O等待时间,提升系统吞吐量
总结
以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。