Java线程池 之 ThreadPoolExecutor
阿里巴巴的Java开发手册规定:
- 【强制】对于服务端需要长期运行的程序,线程池不允许使用Executors.newXXXThreadPool()创建,因为这种方式会使用无界的任务队列,为避免OOM,建议通过ThreadPoolExecutor的构造方法创建,这样可以让使用者更加明确线程池的运行规则,规避资源耗尽的风险。 说明:Executors各个方法的弊端: 1)newFixedThreadPool和newSingleThreadExecutor: 主要问题是允许的请求队列的长度为Integer.MAX_VALUE,可能会堆积大量的请求,会耗费非常大的内存,甚至OOM。 2)newScheduledThreadPool和newCachedThreadPool: 主要问题是允许的创建线程数量为Integer.MAX_VALUE,可能会创建数量大量的线程,甚至OOM。
- 【强制】创建线程或线程池时请指定有意义的线程名称,方便出错时回溯。
创建线程池的正确姿势是直接调用ThreadPoolExecutor的构造函数创建线程池。在创建的同时,指定线程的数量和BlockQueue容量就可以了,该构造方法的定义如下:
public ThreadPoolExecutor(int corePoolSize, //核心线程数(线程池长期维护的线程数),一线程处于空闲状态,也不会回收
int maximumPoolSize, //最大线程数
long keepAliveTime, //超过corePoolSize的线程的空闲时长,超过这个时间,多余的线程会被回收
TimeUnit unit, //时间单位
BlockingQueue workQueue, //线程等待队列
ThreadFactory threadFactory, //线程工厂,新线程的产生方式
RejectedExecutionHandler handler) //线程等待队列已满时的拒绝策略:当任务太多来不及处理时,如何拒绝任务;
其中:
- corePoolSize:线程池中的线程数量,该数量值决定了新添加的任务是开辟新的线程去执行,还是放到workQueue任务队列中去;
- maximumPoolSize:线程池中的最大线程数量,该参数会根据使用的workQueue任务队列的类型,决定线程池会开辟的最大线程数量;
- workQueue:任务队列,被添加到线程池中,但尚未被执行的任务,它一般分为:
- 直接提交队列:SynchronousQueue
- 有界任务队列:ArrayBlockingQueue
- 无界任务队列:LinkedBlockingQueue
- 优先任务队列:PriorityBlockingQueue
- threadFactory:线程工厂,用于创建线程,一般用默认即可;
- handler:拒绝策略,当任务太多来不及处理时,如何拒绝任务。
注意:
- corePoolSize和maximumPoolSize:设置不当会影响效率,甚至耗尽线程;
- workQueue:设置不当容易导致OOM;
- handler:设置不当会导致提交任务时抛出异常。
线程池的工作顺序:corePoolSize -> 任务队列 -> maximumPoolSize -> 拒绝策略
- 当线程数小于核心线程数时,创建线程。
- 当线程数大于等于核心线程数,且任务队列未满时,将任务放入任务队列。
- 当线程数大于等于核心线程数,且任务队列已满
- 若线程数小于最大线程数,创建线程
- 若线程数等于最大线程数,抛出异常,拒绝任务
可以向线程池提交的任务有Runnable和Callable两种,其中,Callable是JDK1.5时加入的接口,作为Runnable的一种补充,允许有返回值,允许抛出异常。
ThreadPoolExecutor提供了以下三种提交任务的方法
- void execute(Runnable command);
- Future submit(Callable task)
- Future submit(Runnable task) :虽然返回Future,但是其get()方法总是返回null
- Future submit(Runnable task, T result)
若无特别说明,下面示例中使用到的ThreadTask的代码如下:
public class ThreadTask implements Runnable {
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
}
示例1、直接提交队列(SynchronousQueue)
直接提交队列是一个特殊的BlockingQueue,它没有容量,每执行一个插入操作就会阻塞,需要再执行一个删除操作才会被唤醒,反之每一个删除操作也都要等待对应的插入操作。 使用SynchronousQueue队列,提交的任务不会被保存起来,总是会马上提交执行。如果用于执行任务的线程数量小于maximumPoolSize,则尝试创建新的进程;如果达到maximumPoolSize设置的最大值,则根据你设置的handler执行拒绝策略。 在这种情况下,需要对程序的并发量有个准确的评估,才能设置合适的maximumPoolSize数量,否则很容易就会执行拒绝策略。
public static void main(String[] args) {
ExecutorService pool = new ThreadPoolExecutor(1, 2, 1000,
TimeUnit.MILLISECONDS,
new SynchronousQueue(),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy()
);
for (int i = 0; i
关注
打赏
最近更新
- 深拷贝和浅拷贝的区别(重点)
- 【Vue】走进Vue框架世界
- 【云服务器】项目部署—搭建网站—vue电商后台管理系统
- 【React介绍】 一文带你深入React
- 【React】React组件实例的三大属性之state,props,refs(你学废了吗)
- 【脚手架VueCLI】从零开始,创建一个VUE项目
- 【React】深入理解React组件生命周期----图文详解(含代码)
- 【React】DOM的Diffing算法是什么?以及DOM中key的作用----经典面试题
- 【React】1_使用React脚手架创建项目步骤--------详解(含项目结构说明)
- 【React】2_如何使用react脚手架写一个简单的页面?