1.简单介绍线程池的主要参数:
核心线程数(corePoolSize):
最大线程数(maximumPoolSize):
corePoolSize 线程数量的线程最大空闲时间(keepAliveTime):
时间单位(unit):
阻塞队列(BlockingQueue):
阻塞队列又分为有界队列(如LinkedBlockingQueue)和无界队列(如ArrayBlockingQueue);有界队列容易造成任务丢失,无界队列容易造成资源耗尽;
这几个参数是如何控制线程数量的?
- 如果运行线程数小于corePoolSize,即使当前有空闲线程,提交新任务时也会新建一个线程来运行;
- 如果运行线程数大于或等于corePoolSize且小于maximumPoolSize,新提交的任务就会入列等待,等待有空闲线程;如果队列已满,并且运行线程数小于maximumPoolSize,将会新建一个线程来运行;
- 如果线程数大于maximumPoolSize,新提交的任务将会根据拒绝策略来处理。
ThreadPoolExecutor 定义了四种拒绝策略:
- AbortPolicy:默认策略,在需要拒绝任务时抛出RejectedExecutionException;
- CallerRunsPolicy:直接在 execute 方法的调用线程中运行被拒绝的任务,如果线程池已经关闭,任务将被丢弃;
- DiscardPolicy:直接丢弃任务;
- DiscardOldestPolicy:丢弃队列中等待时间最长的任务,并执行当前提交的任务,如果线程池已经关闭,任务将被丢弃。
我们也可以自定义拒绝策略,只需要实现 RejectedExecutionHandler;需要注意的是,拒绝策略的运行需要指定线程池和队列的容量
2.为什么不直接使用Executors创建线程池
如ExecutorService fixedThreadPool = Executors.newFixedThreadPool(1);
newFixedThreadPool()方法底层封装使用的还是ThreadPoolExcuter,底层原码如下:
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
new ThreadPoolExecutor()创建线程池:
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
RejectedExecutionHandler handler) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
Executors.defaultThreadFactory(), handler);
}
通过源码可以看出,用newFixedThreadPool()创建线程,核心线程数和最大线程数相等,阻塞队列为无界队列;因此该方式的弊端就是存在资源耗尽的风险;
通过ThreadPoolExcuter创建线程池,可灵活的控制核心线程数量、最大线程数量,阻塞队列以及拒绝策略,不建议使用无界队列;
本文参考以下链接,详见:
为什么阿里不允许用Executors创建线程池,而是通过ThreadPoolExecutor的方式?
3.创建线程池时踩的坑:
- 在方法内部创建线程池,而不是创建全局的线程池:
在方法内部创建线程池(局部变量),每当方法被调用一次,就会创建一个新的线程池,如果线程没有及时关闭,最终可能会造成内存溢出;而创建全局变量,只有在java类被编译的时候才会创建线程池,这样大大减少了线程池创建的次数,也充分提现到了线程池的作用;一般用private static finnal修饰; - 注意异常处理;
- 注意threadPoolExecutor.submit和executor的区别;
未完待续。。。。。