线程池ThreadPoolExecutor浅析及使用

203 阅读3分钟

一、Java源码定义

首先我们来看下jdk所定义的ThreadPoolExecutor接口信息

public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler) 

参数

  • corePoolSize:线程池中需要保持的核心线程数,即使这些线程处于空闲状态
  • maxmumPoolSize:线程池中所允许的最大线程数
  • keepAliveTime:当池中线程数超过核心线程数时,超出部分线程消亡前的空闲时间
  • unit:keeepAliveTime的时间单元
  • workQueue:由execute方法所提交的Runnable任务在未执行前所存储的队列
  • threadFactory:创建线程的线程创建工厂
  • handler:拒绝策略-线程池满了&队列也满了

二、实现原理

线程池启动后默认是没有线程的,当有task时才去创建线程来执行。随着task增加,线程池中的线程数达到corepoolsize时,线程数不再增加,新提交的task加入到workQueue中。随着task继续增加,workQueue队列满时(workQueue为有界队列)线程池会继续创建新的线程,直至线程数达到maxmumPoolSize。如果task继续提交,此时线程池将依据handler策略对新提交的task执行拒绝操作。洪峰过后,当线程池中线程处于空闲状态时间超过keepAliveTime时,线程消亡,直至线程数减少到corePoolSize。 上诉即整个线程池运行流程,此处需要说明,如果线程池队列为无界队列,而提交任务又无节制的话,洪峰来临时,很容易出现OOM。

三、任务缓存队列

workQueue的类型分为以下三种:

  • ArrayBlockingQueue:基于数组的FIFO有界队列。
  • LinkedBlockingQueue:基于链表的FIFO有界队列,默认队列长度为Integer.MAX_VALUE。
  • SynchronousQueue:此队列并不保存新提交的task,而是直接创建新的线程来执行task。

四、拒绝策略

上诉的RejectedExecutionHandler通常有以下几种:

  • AbortPolicy:对齐task,抛出RejectedExecutionException。
  • CallerRunsPolicy:线程池未shut down时,交由task提交线程直接来执行。
  • DiscardOldestPolicy:丢弃workQueue里最旧的task,然后重新执行。
  • DiscardPolicy:静静的丢弃当前task,此处未抛异常。

五、关闭线程池

线程池提供了两种关闭方式,如下:

  • shutdown:优雅关闭方式,此种方式线程池不再接受新的task,等队列里所有task执行完后关闭。
  • shutdownNow:暴力关闭方式,此种方式terminate正在执行的task,clear任务队列,并返回未执行成功的task。

六、线程池监控

通常我们需要监控线程池的状态以及相关信息,可以获取以下几个参数来判断线程池所运行的信息:

  • getActiveCount:当前正在执行任务的线程数。
  • getCompletedTaskCount:返回已经完成的任务数(近似值)。
  • getLargestPoolSize:线程池曾经创建过的最大的线程数。
  • getPoolSize:返回当前线程池线程数。
  • getTaskCount:返回所有提交到线程池的任务数。

七、线程池大小配置

线程池的大小配置基于cpu以及task类型来调节,不过基准参考可以参考如下:

  • 如若task为CPU密集型可以配置CPU数+1。
  • 如若task为IO密集型可以配置CPU数*2。 然后再基于实际task运行情况来微调线程池的大小。