阿里的 Java开发手册建议线程池不允许使用 Executors 去创建,而是通过 ThreadPoolExecutor 的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。
禁用原因:Executors创建出来的线程池使用的全都是无界队列,而使用无界队列就可以无限保存任务,因此很有可能造成OOM异常。同时在某些类型的线程池里面,使用无界队列还会导致maxinumPoolSize、keepAliveTime、handler等参数失效。
ThreadPoolExecutor
其中ThreadPoolExecutor表示一个线程池,Executors类扮演着线程工厂的角色,通过Executors可以取得一个拥有特定功能的线程池。ThreadPoolExecutor类实现了Executor接口,通过这个接口,任何Runnable的对象都可以被ThreadPoolExecutor线程池调度。
public ThreadPoolExecutor(int corePoolSize, // 线程池核心池大小
int maximumPoolSize, // 线程池最大线程数
long keepAliveTime, // 当线程数大于核心池大小时,多余的空闲线程等待新任务的最长时间,超过时间会被销毁
TimeUnit unit, // keepAliveTime的时间单位
BlockingQueue<Runnable> workQueue, // 用来存储等待执行任务的队列
ThreadFactory threadFactory, // 线程工厂
RejectedExecutionHandler handler) // 拒绝策略
参数详解
corePoolSize 和 maximumPoolSize,keepAliveTime
当corePoolSize和maximunPoolSize的值均为1,keepAliveTime设置为0因为不会出现存在空闲线程的情况,这样线程池中就会一直只存在一个线程,即单线程的线程池SingleThreadExecutor,它可以保证任务顺序执行。
当corePoolSize = 0 并且 maximumPoolSize = Integer.MAX_VALUE,称为线程数大小无界的线程池CachedThreadPool,因为最大的线程数是Integer.MAX_VALUE,所以可能创建数量很多的线程导致OOM。
TimeUnit
- TimeUnit.DAYS // 天
- TimeUnit.HOURS // 小时
- TimeUnit.MINUTES // 分钟
- TimeUnit.SECONDS // 秒
- TimeUnit.MILLISECONDS // 毫秒
BlockingQueue
- ArrayBlockingQueue :由数组结构组成的有界阻塞队列。当任务队列装满时,可能将线程数提升到corePoolSize以上。
- LinkedBlockingQueue :由链表结构组成的有界阻塞队列。除非资源耗尽,否则无界的任务队列不存在入队失败的情况。
- PriorityBlockingQueue :支持优先级排序的无界阻塞队列。总是保证高优先级的任务先执行。
- DelayQueue: 使用优先级队列实现的无界阻塞队列。
- SynchronousQueue: 直接提交队列,不存储元素的阻塞队列。提交的任务不会被保存,而是总将新任务提交给线程执行。如果没有空闲的进程,则尝试创建新的进程,如果进程数量已经达到最大值,则执行拒绝策略。
- LinkedTransferQueue: 由链表结构组成的无界阻塞队列。
- LinkedBlockingDeque: 由链表结构组成的双向阻塞队列。
ThreadFactory
用户可以实现它自定义自己的线程启动方式,可以设置线程名称、类型以及优先级等属性。其中也只是有一个newthread方法。
RejectedExecutionHandler
- ThreadPoolExecutor.AbortPolicy: 丢弃任务并抛出RejectedExecutionException异常,也是线程池默认的策略。
- ThreadPoolExecutor.DiscardPolicy:也是丢弃任务,但是不抛出异常。
- ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程)
- ThreadPoolExecutor.CallerRunsPolicy:由调用线程处理该任务
线程池执行流程
- 先判断线程池中线程的数量是否小于核心线程数,如果小于corePoolSize,就创建新的线程去执行任务;否则就进入到下面流程。
- 判断任务队列是否已经满了,即:判断workQueue有没有满,如果没有满,就将任务添加到任务队列中;如果已经满了,就进入到下面的流程。
- 再判断如果新创建一个线程后,线程数是否会大于最大线程数,如果大于maximumPoolSize,则进入到下面的流程;否则就创建一个新的线程来执行任务。
- 执行拒绝策略,即执行handler的rejectedExecution()方法。