目录
前言:
上一篇介绍的是直接使用ThreadPoolExecutor来创建线程池,很多人也称通过ThreadPoolExecutor创建线程池为自定义线程池
Executor其实还提供了三种类型的线程池:
FixedThreadPool
先来看一下FixThreadPool的构造函数:
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
public static ExecutorService newFixedThreadPool(int nThreads, ThreadFactory threadFactory) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>(),
threadFactory);
}
会发现底层其实用的就是ThreadPoolEexcutor来实现的,只不过是参数已经写死了
参数:
核心线程数: nThreads 外面传递进来
最大线程数: 等于核心线程数
时间: 0
时间单位: 毫秒
队列: new LinkedBlockingQueue<Runnable> 无限大
public LinkedBlockingQueue() {
this(Integer.MAX_VALUE);
}
执行流程图:
我们从参数上可以发现它的核心线程数就等于最大线程数
代码测试:
执行流程
- 如果当前运行的线程数小于 corePoolSize, 如果再来新任务的话,就创建新的线程来执行任务;
- 当前运行的线程数等于 corePoolSize 后, 如果再来新任务的话,会将任务加入
LinkedBlockingQueue
; - 线程池中的线程执行完 手头的任务后,会在循环中反复从
LinkedBlockingQueue
中获取任务来执行; 通过take拿
不推荐使用该方式来创建线程池:
由于核心线程数就等于最大线程数,并且阻塞队列是无限增大的,所以当任务过来的时候他会无限的排队所以不推荐使用这种方式,因为会造成OOM。
使用无界队列 LinkedBlockingQueue
作为线程池的工作队列(队列的容量为 Intger.MAX_VALUE)。无界队列作为线程池的工作队列会对线程池带来不好的影响,说简单点就是可能会导致 OOM,队列的本质是数据结构,就是用来存储的,所以队列满了才会出现内存溢出
SingleThreadExecutor
SingleThreadExecutor
是只有一个线程的线程池。下面看看SingleThreadExecutor 的实现:
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
参数:
核心线程数:1
最大线程数:1
线程队列: 和fixed的一样都是无限大
流程图:
- 如果当前运行的线程数少于 corePoolSize,则创建一个新的线程执行任务;
- 当前线程池中有一个运行的线程后,将任务加入
LinkedBlockingQueue
- 线程执行完当前的任务后,会在循环中反复从
LinkedBlockingQueue
中获取任务来执行;
同样也不推荐使用sing线程池,因为他和fix线程池一样都是使用无界队列作为线程池队列,所以会造成OOM,sing线程池适合的业务场景就是需要按照顺序来依次执行,但是效率会很低
CachedThreadPool
是一个会根据需要不断创建新线程的线程池
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
public static ExecutorService newCachedThreadPool(ThreadFactory threadFactory) {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>(),
threadFactory);
}
CacheThreadPool的corePoolSize被设置为0,但是maximumPoolSize被设置为最大值,几乎是无界的,也就意味着如果主线程提交任务的速度高于线程处理任务的速度,CachedThreadPool就会不断的创建新的任务,但是这个线程池的阻塞队列用的是SynchronousQueue
SynchronousQueue队列的特点:
队列中同时只有一个节点,只能装一个数据,只有当前的数据被消费了才能继续装新的数据 (一夫一妻制)传球手,这个特点不会导致Cache线程池先出OOM,但是由于它的最大线程数为无限大会出现CPU爆满的问题
执行流程图:
从图上我们可以发现,几乎所有的任务都创建线程了,因为maximumPoolSize是无界的,所以提交任务的速度 > 线程池中线程处理任务的速度就要不断创建新线程;每次提交任务,都会立即有线程去处理,因此CachedThreadPool适用于处理大量、耗时少的任务
项目中用了这个Cahce线程池出现的问题:
所以还是要谨慎消息使用这个线程,在项目中我们在汇总成绩部分用到了线程池,但是由于同事使用了这个Cache线程池来执行了批量导出的任务,导致任务多的时候CPU打满了,后来通过线上排查发现所有的线程都在执行这个任务,导致其他工作无法正常进行,后来将这部分优化为使用threadPoolExecutor来执行线程任务,性能提升了很多,不会出现CPU打满的情况
Executors 返回线程池对象的弊端如下:
FixedThreadPool
和SingleThreadExecutor
: 允许请求的队列长度为 Integer.MAX_VALUE,可能堆积大量的请求,从而导致 OOM。- CachedThreadPool 和 ScheduledThreadPool : 允许创建的线程数量为 Integer.MAX_VALUE ,可能会创建大量线程,从而导致 CPU打满。