java-线程池详解
1. 创建方式
1.1 newFixedThreadPool
// 创建固定线程数是5的线程池·
ExecutorService executorService= Executors.newFixedThreadPool(5);
1.1.1 介绍
FixedThreadPool 被称为可重用固定线程数的线程池。通过 Executors 类中的相关源代码来看一下相关实现:
1.1.1.2 执行过程
1.1.1.3 缺点
- 使用无界队列LinkedBlockingQueue,容易照成OOM
- 线程池的线程数时固定的,容易照成CPU空闲。
1.1.2 newSingleThreadExecutor
// 创建线程数是1的线程池
ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();
1.1.2.1 介绍
SingleThreadExecutor 是只有一个线程的线程池。下面看看SingleThreadExecutor 的实现:
1.1.2.2 执行过程
1.1.2.3 缺点
- 使用无界队列LinkedBlockingQueue,容易照成OOM。
- 照成CPU空闲浪费。
1.1.3 newCachedThreadPool
// 创建无固定数量的线程池
ExecutorService executorService= Executors.newCachedThreadPool();
1.1.3.1 介绍
CachedThreadPool 是一个会根据需要创建新线程的线程池。下面通过源码来看看 CachedThreadPool 的实现:
1.1.3.2 执行过程
- 首先执行 SynchronousQueue.offer(Runnable task) 提交任务到任务队列。如果当前 maximumPool 中有闲线程正在执行SynchronousQueue.poll(keepAliveTime,TimeUnit.NANOSECONDS),那么主线程执行 offer 操作与空闲线程执行的 poll 操作配对成功,主线程把任务交给空闲线程执行,execute()方法执行完成,否则执行下面的步骤 2。
- 当初始 maximumPool 为空或者 maximumPool 中没有空闲线程时,将没有线程执行 SynchronousQueue.poll(keepAliveTime,TimeUnit.NANOSECONDS)。这种情况下,步骤 1 将失败,此时 CachedThreadPool 会创建新线程执行任务,execute 方法执行完成。
1.1.3.3 缺点
- 允许创建的线程数量Integer.MAX_VALUE,会照成OOM。
1.1.4 ThreadPoolExecutor
int coreSize = 10;
int maximumPoolSize = 20;
int keepAliveTime = 5;
TimeUnit unit = TimeUnit.MINUTES;
BlockingQueue<Runnable> workQueue = new ArrayBlockingQueue(50);
ThreadFactory factory = new ThreadFactoryBuilder().setNameFormat("xxx").build();
RejectedExecutionHandler handler = new ThreadPoolExecutor.AbortPolicy();
ExecutorService executorService = new ThreadPoolExecutor(coreSize,maximumPoolSize, keepAliveTime, unit, workQueue, factory, handler);
通过构造方法的方式创建线程池。
1.1.4.1 介绍
ThreadPoolExecutor是线程池实现的核心类,上面其他三种创建方式,最终都是调用它的构造方法进行创建的,通过构造方法,我们可以自由控制线程池的各种参数。
1.1.4.2 执行过程
1.1.4.3 优点
- 灵活性:ThreadPoolExecutor构造方法提供了丰富的参数选项,可以根据实际需求对线程池进行灵活配置。可以指定核心线程数、最大线程数、线程空闲时间、任务队列类型等参数,以满足不同的并发场景。
- 并发控制:线程池可以有效地限制并发线程的数量,防止系统资源过度消耗。通过设置合适的线程池大小,可以平衡系统的并发负载,防止系统因过多的并发请求导致性能下降或崩溃。
1.2 如何优雅的关闭线程池
private static void shutdown(ExecutorService executorService) {
// 第一步:使新任务无法提交
executorService.shutdown();
try {
// 第二步:等待未完成任务结束
if(!executorService.awaitTermination(60, TimeUnit.SECONDS)) {
// 第三步:取消当前执行的任务
executorService.shutdownNow();
// 第四步:等待任务取消的响应
if(!executorService.awaitTermination(60, TimeUnit.SECONDS)) {
System.err.println("Thread pool did not terminate");
}
}
} catch(InterruptedException ie) {
// 第五步:出现异常后,重新取消当前执行的任务
executorService.shutdownNow();
Thread.currentThread().interrupt(); // 设置本线程中断状态
}
}
1.3 配置线程池
1.3.1 重要参数
1、corePoolSize:核心线程数
* 核心线程会一直存活,及时没有任务需要执行
* 当线程数小于核心线程数时,即使有线程空闲,线程池也会优先创建新线程处理
* 设置allowCoreThreadTimeout=true(默认false)时,核心线程会超时关闭
2、queueCapacity:任务队列容量(阻塞队列)
* 当核心线程数达到最大时,新任务会放在队列中排队等待执行
3、maxPoolSize:最大线程数
* 当线程数>=corePoolSize,且任务队列已满时。线程池会创建新线程来处理任务
* 当线程数=maxPoolSize,且任务队列已满时,线程池会拒绝处理任务而抛出异常
4、 keepAliveTime:线程空闲时间
* 当线程空闲时间达到keepAliveTime时,线程会退出,直到线程数量=corePoolSize
* 如果allowCoreThreadTimeout=true,则会直到线程数量=0
5、allowCoreThreadTimeout:允许核心线程超时
6、rejectedExecutionHandler:任务拒绝处理器
* 两种情况会拒绝处理任务:
- 当线程数已经达到maxPoolSize,切队列已满,会拒绝新任务
- 当线程池被调用shutdown()后,会等待线程池里的任务执行完毕,再shutdown。如果在调用 shutdown()和线程池真正shutdown之间提交任务,会拒绝新任务
* 线程池会调用rejectedExecutionHandler来处理这个任务。如果没有设置默认是AbortPolicy, 会抛出异常
* ThreadPoolExecutor类有几个内部实现类来处理这类情况:
- AbortPolicy 丢弃任务,抛运行时异常
- CallerRunsPolicy 执行任务
- DiscardPolicy 忽视,什么都不会发生
- DiscardOldestPolicy 从队列中踢出最先进入队列(最后一个执行)的任务
* 实现RejectedExecutionHandler接口,可自定义处理器
1.3.2 饱和策略
- AbortPolicy :在默认的 ThreadPoolExecutor.AbortPolicy 中,处理程序遭到拒绝将抛出运行时 RejectedExecutionException。
- CallerRunsPolicy:在 ThreadPoolExecutor.CallerRunsPolicy 中,线程调用运行该任务的 execute 本身。此策略提供简单的反馈控制机制,能够减缓新任务的提交速度。
- DiscardPolicy:在 ThreadPoolExecutor.DiscardPolicy 中,不能执行的任务将被删除。
- DiscardOldestPolicy:在 ThreadPoolExecutor.DiscardOldestPolicy 中,如果执行程序尚未关闭,则位于工作队列头部的任务将被删除,然后重试执行程序(如果再次失败,则重复此过程)。
1.4 线程池执行过程
- 当正在运行的线程小于corePoolSize,线程池会创建新的线程。
- 当大于corePoolSize而任务队列未满时,就会将整个任务塞入队列。
- 当大于corePoolSize而且任务队列满时,并且小于maximumPoolSize时,就会创建新的线程执行任务。
- 当大于maximumPoolSize时,会根据拒绝策略处理线程。
1.5 参数如何设置
- CPU密集型:尽量使用较小的线程池,一般 Cpu核心数+1
- IO密集型:可以使用较大的线程池,一般 CPU核心数 * 2
1.6 线程池监控
通过线程池提供的参数进行监控。线程池里有一些属性在监控线程池的时候可以使用:
- taskCount:线程池需要执行的任务数量。
- completedTaskCount:线程池在运行过程中已完成的任务数量,小于或等于taskCount。
- largestPoolSize:线程池曾经创建过的最大线程数量。通过这个数据可以知道线程池是否满过。如等于线程池的最大大小,则表示线程池曾经满了。
- getPoolSize:线程池的线程数量。如果线程池不销毁的话,池里的线程不会自动销毁,所以这个大小只增不+ getActiveCount:获取活动的线程数。
通过扩展线程池进行监控。通过继承线程池并重写线程池的 beforeExecute,afterExecute和terminated方法,我们可以在任务执行前,执行后和线程池关闭前干一些事情。如监控任务的平均执行时间,最大执行时间和最小执行时间等。