线程池的创建&&工作机制
1、线程池的作用
为什么需要线程池:
- 线程的管理比较复杂(比如什么时候新增线程、什么时候减少空闲线程)
- 任务存取比较复杂(什么时候接受任务、什么时候拒绝任务、保证多线程不抢到同一个任务)
线程池的作用:轻松管理线程、协调任务的执行过程
2、线程池的创建和使用
2.1自己写线程池:
线程池的实现是很麻烦的,有些大厂的面试题会要我们实现线程池,这个时候就可以根据线程池的作用来分析编写啦.(比如什么时候新增线程、什么时候减少空闲线程)
2.2使用Java提供的或者第三方提供的
不用自己写,如果是在Spring中,可以用ThreadPoolTaskExecutor配合Async注解来实现。(不太建议)
如果是在Java中,可以使用JUC并发编程包中的ThreadPoolExecutor来实现非常灵活地自定义线程池。
Java线程池有七大参数
public ThreadPoolExecutor(
int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler
)
2.2.1线程池的参数含义
怎么确定线程池参数?结合实际情况(实际业务场景和系统资源)来测试调整,不断优化、
回归到该系统中,考虑该系统最脆弱的环节(系统的瓶颈)在哪里?
现有条件:比如AI生成能力的并发是允许4个任务同时去执行,AI能力允许20个任务排队
int corePoolSize(核心线程数):正常情况下,系统应该能同时工作的线程数(随时就绪状态)
int maximumPoolSize(最大线程数):极限情况下,线程池可容纳的最多线程
long keepAliveTime(空闲线程存活时间):非核心线程在没有任务的情况下,过多久要删除,释放无用的资源
TimeUnit unit(空闲线程存活时间的单位):分钟、秒
BlockingQueue workQueue(工作队列):用于存放给线程执行的任务的队列,该队列应当设置长度,不能为无限队列,那样也会占用额外的资源
ThreadFactory threadFactory(线程工厂):控制每个线程的生成、线程的属性(比如线程名)
RejectedExecutionHandler handler(拒绝策略):任务队列满的时候,采取什么措施。抛异常?不抛异常?自定义策略?
资源隔离策略:比如重要的任务(VIP任务)一个队列,普通任务一个队列,保证这两个队列互不干扰
2.2.2线程池工作机制
这里有用例图解说版 和 流程图版的
用例图解说版:没那么直观,但也清晰,细节到位
流程图版:更清晰直观,当部分细节没有
推荐两个都看
用例图解说:
刚开始,没有任何的线程,也没有任何的任务:
来了一个任务,发现我们的员工还没有达到正式员工数(corePoolSize = 2),来一个员工直接处理这个任务
又来了一个任务,发现我们的员工还没有达到正式员工数(corePoolSize = 2),再来一个员工直接处理这个任务
又来了一个任务,但是我们正式员工数已经满了(当前线程数 = corePoolSize = 2),任务放到队列(最大长度 workQueue.size 是 2)里等待,而不是再加新员工。
又来了一个任务,但是我们的任务队列已经满了(当前线程数 > corePoolSize = 2,已有任务数 = 最大长度 workQueue.size = 2),新增线程(maximumPoolSize = 4)来处理新任务,而不是丢弃任务
已经到了任务 7,但是我们的任务队列已经满了、临时工也招满了(当前线程数 = maximumPoolSize = 4,已有任务数 = 最大长度 workQueue.size = 2),调用 RejectedExecutionHandler 拒绝策略来处理多余的任务。
如果当前线程数超过 corePoolSize(正式员工数),又没有新的任务给他,那么等 keepAliveTime 时间达到后,就可以把这个线程释放。
流程图:
2.2.3线程池的参数如何设置
现有条件:比如AI生成能力的并发是允许4个任务同时去执行,AI能力允许20个任务排队
int corePoolSize(核心线程数):正常情况下,可以设置为2-4
int maximumPoolSize(最大线程数):设置为极限情况<=4
long keepAliveTime(空闲线程存活时间):一般为秒级或分钟级
TimeUnit unit(空闲线程存活时间的单位):分钟、秒
BlockingQueue workQueue(工作队列):结合实际情况去设置,可以设置为20
RejectedExecutionHandler handler(拒绝策略):抛异常,标记数据库的任务状态为“任务满了已拒绝”
一般情况下,任务分为IO密集型和计算密集型两种
计算密集型:吃CPU,比如音视频处理、图像处理、数学计算等,一般设置corePoolSize为CPU核心数+1(空余线程),可以让每个线程都能利用好CPU的每个核,而且线程之间不用频繁切换
IO密集型:吃带宽/内存/硬盘等资源,corePoolSize可以设置大一些,一般为2n左右,但是建议以IO能力为主
例:导入百万数据到数据库属于IO密集型任务
2.2.4调试
自定义线程池:
@Configuration
public class ThreadPoolExecutorConfig {
@Bean
public ThreadPoolExecutor threadPoolExecutor() {
ThreadFactory threadFactory = new ThreadFactory() {
private int count = 1;
@Override
public Thread newThread(@NotNull Runnable r) {
Thread thread = new Thread(r);
thread.setName("线程" + count);
count++;
return thread;
}
};
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(2, 4, 100, TimeUnit.SECONDS, new ArrayBlockingQueue<>(4), threadFactory);
return threadPoolExecutor;
}
提交任务到线程池:
@GetMapping("/add")
public void add(String name) {
CompletableFuture.runAsync(() -> {
System.out.println("任务执行中:" + name + ",执行人:" + Thread.currentThread().getName());
try {
Thread.sleep(60000);
} catch (InterruptedException e) {
e.printStackTrace();
}
},threadPoolExecutor);
}