线程池在Java中通常由
java.util.concurrent.ThreadPoolExecutor
类实现,其主要参数包括:
- corePoolSize(核心线程数)
- maximumPoolSize(最大线程数)
- keepAliveTime(空闲线程存活时间)
- unit(存活时间单位)
- workQueue(任务队列)
- threadFactory(线程工厂)
- handler(拒绝策略)
这些参数的配置需要根据具体的应用场景来调整(可以搭配属性配置文件进行动态配置)。以下是根据不同实际场景的一些配置建议:
1. corePoolSize(核心线程数)
含义:线程池中的常驻核心线程数,即使线程池中没有任务,这些线程也不会被销毁。
配置建议:
- CPU密集型任务(例如大量的计算任务):核心线程数通常设置为CPU核心数(例如,如果有8个CPU核心,corePoolSize可以设置为8或8+1)。
- I/O密集型任务(例如网络请求、文件读写等):核心线程数可以设置为CPU核心数的2倍或更多,因为I/O操作可能会阻塞线程。
2. maximumPoolSize(最大线程数)
含义:线程池中能够容纳的最大线程数。
配置建议:
- CPU密集型任务:通常设置为核心线程数的2-4倍。例如,如果corePoolSize设置为8,maximumPoolSize可以设置为16-32。
- I/O密集型任务:可以设置为更大的值,例如核心线程数的3-4倍,以处理更多的并发任务。
3. keepAliveTime(空闲线程存活时间)
含义:当线程数超过核心线程数时,多余的线程的空闲时间,超过这个时间多余的线程将被销毁。
配置建议:
- 长时间任务处理:可以设置较长的keepAliveTime,例如60秒或更多,以防止频繁创建和销毁线程。
- 短时间任务处理:可以设置较短的keepAliveTime,例如10-30秒,以避免长时间占用资源。
4. unit(存活时间单位)
含义:keepAliveTime的时间单位。
配置建议:一般选择秒(TimeUnit.SECONDS
)或毫秒(TimeUnit.MILLISECONDS
)作为单位,具体取决于keepAliveTime的值。例如,TimeUnit.SECONDS
是最常用的单位。
5. workQueue(任务队列)
含义:用于保存等待执行任务的队列。
配置建议:
- 无界队列(如
LinkedBlockingQueue
):适合任务提交速度快但处理速度较慢的情况,避免任务丢失。 - 有界队列(如
ArrayBlockingQueue
):适合对资源有严格控制的场景,可以避免内存过度消耗。 - 同步队列(如
SynchronousQueue
):适合任务提交和处理速度几乎一致的情况,可以使线程池更紧凑,但不支持缓存任务。
6. threadFactory(线程工厂)
含义:用于创建线程池中工作线程的工厂。
配置建议:
- 使用默认的线程工厂或自定义工厂。自定义线程工厂可以用来设置线程的名称、优先级和线程组,以便于调试和管理。
7. handler(拒绝策略)
含义:当任务队列满了且工作线程数达到最大线程数时,线程池会执行拒绝策略来处理新提交的任务。
配置建议:
- AbortPolicy:默认策略,直接抛出
RejectedExecutionException
异常,适用于不能容忍任务丢失的情况。 - CallerRunsPolicy:将任务回退给调用者线程处理,适用于需要控制任务量而不是丢弃任务的情况。
- DiscardPolicy:丢弃新提交的任务,适用于允许丢弃任务而不影响系统稳定性的情况。
- DiscardOldestPolicy:丢弃队列中最旧的任务,然后尝试执行新任务,适用于允许丢弃旧任务而执行新任务的情况。
示例配置
假设你有一个需要处理大量I/O密集型任务的系统,以下是一个可能的线程池配置:
/**
* 在实际业务中,thread Runnable Callable启动线程都不用,
* 将所有的多线程异步任务都交给线程池执行-资源控制
*
* public ThreadPoolExecutor(int corePoolSize,
* int maximumPoolSize,
* long keepAliveTime,
* TimeUnit unit,
* BlockingQueue<Runnable> workQueue,
* ThreadFactory threadFactory,
* RejectedExecutionHandler handler)
*
* 七大参数:
* corePoolSize:核心线程数【一直存在除非设置( allowCoreThreadTimeOut)】
* 线程池,创建好以后就准备就绪的线程数量,就等待来接受异步任务去执行
* maximumPoolSize:【200】最大线程数量,控制资源
* keepAliveTime:存活时间。如果当前的线程数量大于core数量
* 释放空闲的线程(maximumPoolSize-corePoolSize)。只要线程空闲大于指定的keepAliveTime就释放
* TimeUnit:时间单位
* BlockingQueue:阻塞队列,用来存储等待执行的任务,如果当前对线程的需求超过了 corePoolSize
* 大小,就会放在这里等待空闲线程执行。
* ThreadFactory:创建线程的工厂,比如指定线程名等
* RejectedExecutionHandler:拒绝策略,如果线程满了,线程池就会使用拒绝策略。
*
* 工作顺序:
* 运行流程:
* 1、线程池创建,准备好 core 数量的核心线程,准备接受任务
* 2、新的任务进来,用 core 准备好的空闲线程执行。
* (1) 、core 满了,就将再进来的任务放入阻塞队列中。空闲的 core 就会自己去阻塞队
* 列获取任务执行
* (2) 、阻塞队列满了,就直接开新线程执行,最大只能开到 max 指定的数量
* (3) 、max 都执行好了。Max-core 数量空闲的线程会在 keepAliveTime 指定的时间后自
* 动销毁。最终保持到 core 大小
* (4) 、如果线程数开到了 max 的数量,还有新任务进来,就会使用 reject 指定的拒绝策
* 略进行处理
* 3、所有的线程创建都是由指定的 factory 创建的。
*
* new LinkedBlockingDeque<>(100000) 默认是Integer的最大值。内存不够 / 业务需求
*
* 面试:
* 一个线程池 core 7; max 20 ,queue:50,100 并发进来怎么分配的;
* 先有 7 个能直接得到执行,接下来 50 个进入队列排队,在多开 13 个继续执行。现在 70 个
* 被安排上了。剩下 30 个默认拒绝策略。
* 如果不想抛弃还要执行:使用CallerRunsPolicy策略
* public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {//同步执行线程的run方法
* if (!e.isShutdown()) {
* r.run();
* }
*
* @param args
*/
ThreadPoolExecutor executor = new ThreadPoolExecutor(
10, // corePoolSize
20, // maximumPoolSize
60, // keepAliveTime
TimeUnit.SECONDS, // unit
new LinkedBlockingQueue<>(100), // workQueue
new ThreadFactory() { // threadFactory
@Override
public Thread newThread(Runnable r) {
Thread t = new Thread(r);
t.setName("CustomThreadPoolThread");
return t;
}
},
new ThreadPoolExecutor.CallerRunsPolicy() // handler
);
总结
线程池的参数配置应根据具体的应用场景、负载特性和系统资源来调整。通过监控系统性能和任务执行情况,可以动态调整这些参数,以优化线程池的性能和资源使用。