1.线程池参数说明
(以JUC包提供为例):
corePoolSize(核心线程数) => 正式员工数:正常情况下我们的系统能同时工作的线程数(随时就绪)
maximumPoolSize(最大线程数) => 哪怕任务再多,也最多招多少人:极限情况,线程池最多有多少个线程
keepAliveTime(存活时间) => 空闲线程存活时间 : 非核心线程在没有任务的情况下,过多久要删除,可以释放无用的线程资源
TimeUnit (单位) => 空闲线程存活时间单位
workQueue(工作队列)=> 用于存放给线程执行的任务,存在一个队列的长度(一定要设置,会占用资源)
threadFactory(线程工厂) => 控制每个线程的生成、线程的属性(比如线程名)
handler(拒绝策略) => 拒绝策略:任务队列满的时候采取什么措施,比如抛异常、不抛异常、自定义策略
2.线程池执行机制
假设参数:核心线程2、最大线程4、等待时间20分钟、队列容量2、自定义线程工厂、默认的拒绝策略
return new ThreadPoolExecutor(2, 4, 20, TimeUnit.MINUTES, new ArrayBlockingQueue<>(2), threadFactory);
任务1来了,核心线程处于随时就位的状态,可以立刻处理任务1;
任务2来了(这里都假设任务的执行时间很长),同样可以交给核心线程处理,目前核心线程都在工作;
任务3来了,先进行队列中,等待;
任务4来了,同样进行队列中等待;
任务5来了,交给临时线程5来处理任务5(默认处理新来的任务,而不是队列里面的任务);
任务6来了,同样也可以交给临时线程6处理;
任务7来了,这时候核心线程、临时线程都在工作,队列也满了,就会执行拒绝策略;
当临时线程执行任务完毕,等待了20分钟(keepAliveTime)时间,没有新的任务执行,那么临时线程会被关闭;
⭐ 重点:核心线程满了,新的任务会进队列而不是开临时线程!
3.图解线程池
3.1初始参数
初始状态:核心线程4、最大线程6、等待时间20分钟、队列容量5、自定义线程工厂、默认的拒绝策略
以公司为例子:
- 核心线程:正式员工
- 最大线程:总员工数(当减去正式员工,就是雇的临时员工数量)
- 等待时间:等多长时间,雇的员工都还没项目干,就把这些临时员工开除
- 队列容量:任务的个数
- 自定义线程工厂:给每个员工装饰一下,相当于每个员工的号牌(给线程起名字啥的)
return new ThreadPoolExecutor(4, 6, 20, TimeUnit.MINUTES, new ArrayBlockingQueue<>(5), threadFactory);
3.2执行步骤
当任务1、任务2、任务3、任务4按时间顺序来了,都被正式员工接了;队列也还是空的;临时员工还没被雇;
当任务5来的时候,先进阻塞队列中,不是老板直接就招新员工了,正式员工干完当前的活就可以再干,好老板会排好期的!
当任务6、7、8、9相继而来,任务队列位置有空余,继续放(只要老板够压榨,队列容量使劲拉长,正式员工996+吧)
当还有新任务10来时,老板实在是忙不过来,要招实习,招的员工小E开始接任务10(默认策略,可修改)
当任务11来的时候,继续招人小F实习生来做
当又来了新的任务12,且实习生小E、小F也忙不过来了的时候,可能项目钱也不是很多,划不来,老板也不招不了更多的人了,就不做这个任务了(这就是拒绝策略)
当实习生(临时员工)的活干完了,等了一段时间(keepAliveTime)又没新任务来,就把小E和小F开了,说:”下次有任务了再招你们哈“ 🙃
其实大部分的时间应该都是正式员工在有条不紊的干着...
4.Java代码实现(spring boot)
4.1定义线程池配置类
@Configuration
public class BiThreadPoolExecutor {
// 定义自己的线程工厂
private final ThreadFactory threadFactory = new ThreadFactory() {
private int count;
@Override
public Thread newThread(@NotNull Runnable r) {
// 其实就是创建线程的一些额外操作
Thread thread = new Thread(r);
thread.setName(String.valueOf(count));
count++;
return thread;
}
};
@Bean
public ThreadPoolExecutor threadPoolExecutor() {
return new ThreadPoolExecutor(4, 6, 20, TimeUnit.MINUTES, new ArrayBlockingQueue<>(5), threadFactory);
}
}
4.2测试接口定义
package com.yupi.springbootinit.controller;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ThreadPoolExecutor;
/**
* 线程池测试接口
*/
@RestController
@RequestMapping("/test")
@Slf4j
public class TestController {
@Resource
private ThreadPoolExecutor threadPoolExecutor;
@GetMapping("/pushTask")
public void pushTask(){
// 开启任务,默认的线程吃是ForkJoinPool
CompletableFuture.runAsync(() -> {
System.out.println("当前线程名称:" + Thread.currentThread().getName());
try {
Thread.sleep(600000); // 60分钟
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}, threadPoolExecutor);
}
}
4.3输出结果
调用四次pushTask接口输出:核心线程接到任务
调用第五次接口输出:这时候新的任务进入了任务队列,同样调用第6、7、8、9都是在任务队列中
当第十次调用任务的时候:最大线程数来接管新的任务了,同样11次调用也是如此
当第12次调用的时候,核心线程数、最大线程数、任务队列都满了,这时候会执行拒绝策略,默认抛异常
5.总结
具体的线程池参数一定要根据项目的业务逻辑去设定,第一次肯定是不合适的,只有通过反复的测试,才能确定好线程池参数。