使用线程池的好处
- 降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗
- 提高响应速度。当任务到达时,任务可以不需要等到线程的创建就能立即执行。
- 提高线程的可管理性。线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配、调优和监控。
源码参考
线程池的7大核心参数
public ThreadPoolExecutor(
int corePoolSize, // 核心工作线程(当前任务执行结束后,不会被销毁)
int maximumPoolSize, // 最大工作线程(代表当前线程池中,一共可以有多少个工作线程)
long keepAliveTime, // 非核心工作线程在阻塞队列位置等待的时间
TimeUnit unit, // 非核心工作线程在阻塞队列位置等待时间的单位
BlockingQueue<Runnable> workQueue, // 任务在没有核心工作线程处理时,任务先扔到阻塞队列中
ThreadFactory threadFactory, // 构建线程的线程工厂,可以设置thread的一些信息
RejectedExecutionHandler handler) { // 当线程池无法处理投递过来的任务时,执行当前的拒绝策略
// 初始化线程池的操作
}
- corePoolSize:核心线程数,队列任务未达到队列容量时,最大可以同时运行的线程数量
maximumPoolSize:
最大线程数,队列任务达到队列容量时,最大可以同时运行的线程数量变为最大线程数
workQueue:
新任务来的时候会判断当前运行的线程数量是否达到核心线程数,如果达到,新任务会被存放到队列中
keepAliveTime:
线程池中的线程数量大于核心线程,如果这时没有新的任务提交,核心线程外的线程不会立即销毁,而是会等待,直到等待时间超过
keepAliveTime
,才会回收销毁。
unit:keepAliveTime
参数的单位
handler:
饱和策略
饱和策略
当前同时运行的线程数量达到最大线程数,并且队列已经被放满任务时,触发拒绝策略。
- AbortPolicy:【默认处理】,抛出 RejectedExecutionException 来拒绝新来的任务 ,代表你将丢失对这个任务的处理
- CallerRunsPolicy:【推荐】,当前拒绝策略会在线程池无法处理任务时,由主线程去执行当前任务
- DiscardPolicy:不处理任务,直接丢掉
- DiscardOldestPolicy:将队列中最早的任务丢弃掉,将当前任务再次尝试交给线程池处理
- 自定义policy策略,实现RejectedExecutionHandler
private static class MyRejectedExecution implements RejectedExecutionHandler{
@Override
public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
System.out.println("根据自己的业务情况,决定编写的代码!");
}
}
总结
当我们不指定 RejectedExecutionHandler 饱和策略的话来配置线程池的时候默认使用的是 ThreadPoolExecutor.AbortPolicy。在默认情况下,ThreadPoolExecutor 将抛出 RejectedExecutionException 来拒绝新来的任务 ,这代表你将丢失对这个任务的处理。 对于可伸缩的应用程序,建议使用 ThreadPoolExecutor.CallerRunsPolicy。当最大池被填满时,此策略为我们提供可伸缩队列。
构建线程池,并处理有无返回结果的任务
public static void main(String[] args) throws ExecutionException, InterruptedException {
//1. 构建线程池
ThreadPoolExecutor threadPool = new ThreadPoolExecutor(
2,
5,
10,
TimeUnit.SECONDS,
new ArrayBlockingQueue<>(5),
new ThreadFactory() {
@Override
public Thread newThread(Runnable r) {
Thread thread = new Thread(r);
thread.setName("test-ThreadPoolExecutor");
return thread;
}
},
new MyRejectedExecution()
);
//2. 让线程池处理任务,没返回结果
threadPool.execute(() -> {
System.out.println("没有返回结果的任务");
});
//3. 让线程池处理有返回结果的任务
Future<Object> future = threadPool.submit(new Callable<Object>() {
@Override
public Object call() throws Exception {
System.out.println("我有返回结果!");
return "返回结果";
}
});
Object result = future.get();
System.out.println(result);
//4. 如果是局部变量的线程池,记得用完要shutdown
threadPool.shutdown();
}
private static class MyRejectedExecution implements RejectedExecutionHandler{
@Override
public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
System.out.println("根据自己的业务情况,决定编写的代码!");
}
}
如何正确配置线程池参数
如何区分是CPU密集任务还是IO密集任务?
以下的N是指CPU核心数
IO密集型【推荐核心线程数2N】:涉及到网络读取、文件读取都属于IO密集;大部分时间都花费在处理IO交互上,在处理IO交互时间段内不会占用CPU来处理,这样CPU就可以交给其它线程来使用。
CPU密集型【推荐核心线程数N+1】:这种任务消耗的主要是 CPU 资源,可以将线程数设置为 N(CPU 核心数)+1。比 CPU 核心数多出来的一个线程是为了防止线程偶发的缺页中断,或者其它原因导致的任务暂停而带来的影响。一旦任务暂停,CPU 就会处于空闲状态,而在这种情况下多出来的一个线程就可以充分利用 CPU 的空闲时间
参考美团的动态调参