用法讲解
Executors
创建线程池有多种方式,列举最重要的三种如下:
//创建一个包含指定数目线程的线程池,如果任务数量多于线程数量,那么没有执行的任务必须等待,直到有任务完成为止。 private static ExecutorService threadPool = Executors.newFixedThreadPool(2); //创建单一线程池 此方法可以创建单一线程池,线程池里只有一个线程,单一线程池可以实现以队列的方式来执行任务 private static ExecutorService threadPool1 = Executors.newSingleThreadExecutor(); //创建的是无界线程池,可以进行线程自动回收,此类线程池中存放线程个数理论值为Integer.MAX_VALUE最大值 private static ExecutorService threadPool2 = Executors.newCachedThreadPool();
说明弊端
Executors 各个方法的弊端:
1)newFixedThreadPool 和 newSingleThreadExecutor:
主要问题是堆积的请求处理队列可能会耗费非常大的内存,甚至 OOM。
2)newCachedThreadPool 和 newScheduledThreadPool:
主要问题是线程数最大数是 Integer.MAX_VALUE,可能会创建数量非常多的线程,甚至 OOM。
ThreaPoolExecutor
创建线程池方式只有一种,就是走它的构造函数,参数自己指定:
常用构造函数如下:
参数讲解
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
corePoolSize - 线程池核心池的大小。
maximumPoolSize - 线程池的最大线程数。
keepAliveTime - 当线程数大于核心时,此为终止前多余的空闲线程等待新任务的最长时间。
unit - keepAliveTime 的时间单位。
workQueue - 用来储存等待执行任务的队列。
threadFactory - 线程工厂。
handler - 拒绝策略。
更为详细的介绍查看:点击打开链接
关注点1 线程池大小
线程池有两个线程数的设置,一个为核心池线程数,一个为最大线程数。
在创建了线程池后,默认情况下,线程池中并没有任何线程,等到有任务来才创建线程去执行任务,除非调用了prestartAllCoreThreads()或者prestartCoreThread()方法
当创建的线程数等于 corePoolSize 时,会加入设置的阻塞队列。当队列满时,会创建线程执行任务直到线程池中的数量等于maximumPoolSize。
关注点2 适当的阻塞队列
java.lang.IllegalStateException: Queue full
方法 抛出异常 返回特殊值 一直阻塞 超时退出
插入方法 add(e) offer(e) put(e) offer(e,time,unit)
移除方法 remove() poll() take() poll(time,unit)
检查方法 element() peek() 不可用 不可用
ArrayBlockingQueue :一个由数组结构组成的有界阻塞队列。
LinkedBlockingQueue :一个由链表结构组成的有界阻塞队列。
PriorityBlockingQueue :一个支持优先级排序的无界阻塞队列。
DelayQueue: 一个使用优先级队列实现的无界阻塞队列。
SynchronousQueue: 一个不存储元素的阻塞队列。
LinkedTransferQueue: 一个由链表结构组成的无界阻塞队列。
LinkedBlockingDeque: 一个由链表结构组成的双向阻塞队列。
关注点3 明确拒绝策略
ThreadPoolExecutor.AbortPolicy: 丢弃任务并抛出RejectedExecutionException异常。 (默认)
ThreadPoolExecutor.DiscardPolicy:也是丢弃任务,但是不抛出异常。
ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程)
ThreadPoolExecutor.CallerRunsPolicy:由调用线程处理该任务
线程池创建选择
最近一段时间,个人用上了阿里巴巴的代码校验助手,会提示代码优化,然后本人用Executors创建线程池,被警告了,建议用手动创建线程池的方式,理由很简单:明晰线程池的创建,优化资源的利用。于是就去了解了一下两个的区别。
区别总结:Executors只提供三种常用的创建方式,但是资源配置的参数都是默认的,有时候,我们的线程池配置可能可以有更优的配置,那么Executors就满足不了了,所以Executors不适合定制化线程池。这个时候,我们就要选择ThreadPoolExecutor,
ThreadPoolExecutor实现Executors常用的三种方法
方法1:
Executors.newFixedThreadPool(nThreads);
源码1:
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
模拟1:
private static ExecutorService t = new ThreadPoolExecutor(2, 2,0L, TimeUnit.MILLISECONDS,new LinkedBlockingQueue<Runnable>());
方法2:
Executors.newSingleThreadExecutor();
源码2:
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
模拟2:
new ThreadPoolExecutor(1, 1,0L, TimeUnit.MILLISECONDS,new LinkedBlockingQueue<Runnable>())
方法3:
Executors.newCachedThreadPool()
源码3:
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
模拟3:
new ThreadPoolExecutor(0, Integer.MAX_VALUE,60L, TimeUnit.SECONDS,new SynchronousQueue<Runnable>())
所以Executors实现的三种方法的源码其实是基于ThreadPoolExecutor封装的的。
所以,如果我们自己基于ThreadPoolExecutor去实现线程池,合理的配置各个参数,就可以创建出最合理的线程池。
spring框架实现的线程池配置
(ThreadPoolTaskExecutor在jar:org.springframework:spring-context)
<!-- spring thread pool executor -->
<bean id="taskExecutor" class="org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor">
<!-- 线程池维护线程的最少数量 -->
<property name="corePoolSize" value="10"/>
<!-- 允许的空闲时间 -->
<property name="keepAliveSeconds" value="60"/>
<!-- 线程池维护线程的最大数量 -->
<property name="maxPoolSize" value="10"/>
<!-- 缓存队列 -->
<property name="queueCapacity" value="10"/>
<!-- 对拒绝task的处理策略 -->
<property name="rejectedExecutionHandler">
<bean class="java.util.concurrent.ThreadPoolExecutor$CallerRunsPolicy"/>
</property>
</bean>
理解还比较浅,请博友多多指教