在Java中,我们创建线程池之后,在线程池执行代码过程中它的主要流程是按怎么个逻辑来的?
重点:
线程池是一种池化技术,用于预先创建并管理一组线程,主要为了避免频繁创建和销毁线程的开销,提高性能和响应速度。
创建线程池的几个关键参数配置:核心线程数、最大线程数(数量上包括核心线程数的数量)、空闲(线程)存活时间、工作队列、拒绝策略。
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(20, // 核心线程数
300, // 最大线程数
60, // 空闲(线程)存活时间 60
TimeUnit.SECONDS, // 空闲(线程)存活时间的单位
new ArrayBlockingQueue<>(10), // 工作队列
Executors.defaultThreadFactory(), // 线程工厂,用于创建新线程
new ThreadPoolExecutor.CallerRunsPolicy());// 拒绝策略:任务拒绝处理器(RejectedExecutionHandler),当任务无法执行时的处理策略
// 核心线程数,线程池会一直维护的线程数量,即使这些线程处于空闲状态,也不会被回收
int corePoolSize = 2;
// 最大线程数,线程池允许存在的最大线程数量,包括核心线程和非核心线程
int maximumPoolSize = 4;
// 非核心线程的空闲存活时间,即当非核心线程处于空闲状态超过这个时间,该线程会被回收
long keepAliveTime = 10;
// 时间单位,用于指定 keepAliveTime 的时间单位,例如 TimeUnit.SECONDS 表示秒
TimeUnit unit = TimeUnit.SECONDS;
// 任务等待队列,用于存储等待执行的任务,当核心线程都在执行任务时,新任务会先进入此队列等待
BlockingQueue<Runnable> workQueue = new java.util.concurrent.LinkedBlockingQueue<>();
// 线程工厂,用于创建新线程,可自定义线程的属性,如名称、优先级、是否为守护线程等
ThreadFactory threadFactory = Executors.defaultThreadFactory();
// 拒绝策略,当任务队列已满且线程池中的线程数达到最大线程数时,用于处理新提交的任务,例如抛出异常、丢弃任务等
RejectedExecutionHandler handler = new ThreadPoolExecutor.AbortPolicy();
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
// 核心线程数
corePoolSize,
// 最大线程数
maximumPoolSize,
// 非核心线程的空闲存活时间
keepAliveTime,
// 时间单位
unit,
// 任务等待队列
workQueue,
// 线程工厂
threadFactory,
// 拒绝策略
handler);
主要工作原理:
注意:
创建线程池中设置的参数,核心线程数--核心线程和最大线程数--非核心线程这2者没有什么特殊区分,都是创建的线程。主要线程的创建顺序不同。
线程池中的拒绝策略:
new ThreadPoolExecutor.CallerRunsPolicy();
当任务队列满且没有线程空闲,此时添加任务由即调用者线程执行。适用于希望通过减缓任务提交速度来稳定系统的场景。new ThreadPoolExecutor.AbortPolicy();
当任务队列满且没有线程空闲,此时添加任务会直接抛出 RejectedExecution
错误,这也是默认的拒绝策略。适用于必须通知调用者任务未能被执行的场景。new ThreadPoolExecutor.DiscardOldestPolicy();
当任务队列满且没有线程空闲,会删除最早的任务,然后重新提交当前任务。适用于希望丢弃最旧的任务以保证新的重要任务能够被处理的场景。new ThreadPoolExecutor.DiscardPolicy();
直接丢弃当前提交的任务,不会执行任何操作,也不抛出异常。适用于对部分任务丢弃没有影响的场景,或者系统负载较高时不需要处理所有任务。自定义拒绝策略
可以实现
RejectedExecutionHandler
接口来定义自定义的拒绝策略。例如,在自定义的策略里记录日志或者将任务重新排队。
public class CustomRejectedExecutionHandle implements RejectedExecutionHandler {
@Override
public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
// 在 rejectedExecution 自己实现逻辑,比如记录日志、其他的拒绝逻辑
System.out.println("自定义的决绝策略: " + r.toString() );
}
}
原因:
每创建一个线程都会占用一定的系统资源(如栈空间、线程调度开销等),直接增加线程会迅速消耗系统资源,导致性能下降。
使用阻塞队列可以将任务暂存,避免线程数量无限增长,确保资源利用率更高。
如果阻塞队列都满了,说明此时系统负载很大,再去增加线程到最大线程数去消化任务即可。
创建线程也是有成本的,特别是在系统处于负载大的场景下。
在Java的线程池中,
BlockingQueue<Runnable> workQueue
用于存放待处理的任务。关于任务是由核心线程还是非核心线程处理的问题,我们需要理解线程池的工作机制。线程池中的线程分为核心线程和非核心线程(也称为最大线程)。核心线程数由
corePoolSize
参数指定,最大线程数由maximumPoolSize
参数指定。线程池处理任务的基本流程如下:
\1. 当提交一个新任务时,如果当前运行的线程数小于核心线程数(
corePoolSize
),即使存在空闲线程,线程池也会创建一个新的核心线程来处理任务。\2. 如果当前运行的线程数等于或大于核心线程数,新提交的任务会被放入工作队列(
workQueue
)中等待。\3. 如果工作队列已满,且当前运行的线程数小于最大线程数(
maximumPoolSize
),则线程池会创建新的非核心线程来处理任务。\4. 如果工作队列已满,且当前线程数已达到最大线程数,则根据拒绝策略(
RejectedExecutionHandler
)来处理新任务。关键点:
- 核心线程和非核心线程在创建后并没有本质区别,它们都会从工作队列中获取任务来执行。
- 区别在于回收机制:核心线程默认会一直存活(即使空闲),而非核心线程在空闲一段时间后(由
keepAliveTime
参数指定)会被回收。因此,工作队列中的任务既可能由核心线程处理,也可能由非核心线程处理。具体来说,当有线程(无论是核心还是非核心)空闲时,它就会从工作队列中取出任务来执行。
总结:任务被放入工作队列后,哪个线程(核心或非核心)空闲了,哪个线程就会从队列中取出任务并执行。所以,工作队列中的任务并不指定由核心线程或非核心线程处理,而是由所有线程(包括核心和非核心)共同竞争获取任务执行。
但是,需要注意的是,线程池在创建新线程(核心或非核心)时,都是用来处理新提交的任务(当核心线程都在忙且队列未满时任务入队,队列满时才创建非核心线程)。一旦线程被创建,它们就会不断地从工作队列中取任务执行,直到线程池关闭。
因此,你的问题:
workQueue
中的任务既可能由核心线程处理,也可能由非核心线程处理。这取决于哪个线程空闲。
在Java线程池中,BlockingQueue<Runnable> workQueue
存放的任务既可能由核心线程处理,也可能由非核心线程处理,具体取决于线程池的实时状态。以下是详细规则:
corePoolSize
,线程池会立即创建新的核心线程处理该任务(此时任务不会进入队列)。corePoolSize
,新任务会被放入 workQueue
等待。workQueue
已满,且运行的线程数 小于 maximumPoolSize
,线程池会创建非核心线程处理队列中的任务。maximumPoolSize
,新任务会被拒绝(触发 RejectedExecutionHandler
)。workQueue
中的任务会被所有空闲线程(包括核心和非核心)竞争获取并执行。
例如:
keepAliveTime
后会被销毁,而核心线程默认长期存活(可通过 allowCoreThreadTimeOut(true)
设置核心线程超时回收)。假设线程池配置:corePoolSize=2
, maximumPoolSize=4
, workQueue容量=10
。
workQueue
是所有线程(核心+非核心)共享的任务源。通过这种设计,线程池在保证核心线程高效处理的同时,通过动态扩展非核心线程应对突发负载,最后通过队列缓冲平稳调度任务。