线程池的概念

线程池就是事先将多个线程对象放到一个容器中,当使用的时候就不用 new 线程而是直接去池中拿线程即可,节省了开辟子线程的时间,提高的代码执行

线程池的种类

可缓存线程池

newCachedThreadPool创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。

这种类型的线程池特点是:工作线程的创建数量几乎没有限制(其实也有限制的,数目为 Interger. MAX_VALUE),这样可灵活的往线程池中添加线程。

特点:

  • 线程池中数量没有固定,可达到最大值(Interger. MAX_VALUE)
  • 线程池中的线程可进行缓存重复利用和回收(回收默认时间为 1 分钟)
  • 当线程池中,没有可用线程,会重新创建一个线程

在使用 CachedThreadPool 时,一定要注意控制任务的数量,否则,由于大量线程同时运行,很有会造成系统瘫痪。

指定工作线程数量的线程池

newFixedThreadPool创建一个指定工作线程数量的线程池。每当提交一个任务就创建一个工作线程,如果工作线程数量达到线程池初始的最大数,则将提交的任务存入到池队列中。

FixedThreadPool 是一个典型且优秀的线程池,它具有线程池提高程序效率和节省创建线程时所耗的开销的优点。但是,在线程池空闲时,即线程池中没有可运行任务时,它不会释放工作线程,还会占用一定的系统资源

单线程化的线程池

newSingleThreadExecutor创建一个单线程化的线程池,即只创建唯一的工作者线程来执行任务,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。如果这个线程异常结束,会有另一个取代它,保证顺序执行。

单工作线程最大的特点是可保证顺序地执行各个任务,并且在任意给定的时间不会有多个线程是活动的

定长且支持定时的线程池

newScheduleThreadPool创建一个定长的线程池,而且支持定时的以及周期性的任务执行。例如延迟 3 秒执行。

这 4 种线程池底层全部是 ThreadPoolExecutor 对象的实现,阿里规范手册中规定线程池采用 ThreadPoolExecutor 自定义的,实际开发也是

拥有多个任务队列的线程池

jdk1.8 提供的线程池,底层使用的是ForkJoinPool实现,创建一个拥有多个任务队列的线程池,可以减少连接数,创建当前可用 cpu 核数的线程来并行执行任务

ThreadPoolExecutor 对象参数【7 个】

  • corePoolSize:核心线程数,在 ThreadPoolExecutor 中有一个与它相关的配置:allowCoreThreadTimeOut(默认为 false),当 allowCoreThreadTimeOut 为 false 时,核心线程会一直存活,哪怕是一直空闲着。而当 allowCoreThreadTimeOut
    为 true 时核心线程空闲时间超过 keepAliveTime 时会被回收。
  • maximumPoolSize:最大线程数,线程池能容纳的最大线程数,当线程池中的线程达到最大时,此时添加任务将会采用拒绝策略,默认的拒绝策略是抛出一个运行时错误(RejectedExecutionException)。值得一提的是,当初始化时用的工作队列为 LinkedBlockingDeque 时,这个值将无效。
  • keepAliveTime : 存 活 时 间 , 当 非 核 心 空 闲 超 过 这 个 时 间 将 被 回 收 , 同 时 空 闲 核 心 线 程 是 否 回 收 受 allowCoreThreadTimeOut 影响。
  • unit:keepAliveTime 的单位。
  • workQueue:任务队列,常用有三种队列,即 SynchronousQueue,LinkedBlockingDeque(无界队列),ArrayBlockingQueue(有界队列)。
  • threadFactory:线程工厂,ThreadFactory 是一个接口,用来创建 worker。通过线程工厂可以对线程的一些属性进行定制。默认直接新建线程。
  • RejectedExecutionHandler:也是一个接口,只有一个方法,当线程池中的资源已经全部使用,添加新线程被拒绝时,会调用 RejectedExecutionHandler 的 rejectedExecution 法。默认是抛出一个运行时异常

线程池大小设置

  • 需要分析线程池执行的任务的特性: CPU 密集型还是 IO 密集型
  • 需要分析每个任务执行的平均时长大概是多少,这个任务的执行时长可能还跟任务处理逻辑是否涉及到网络传输以及底层系统资源依赖有关系。

如果是 CPU 密集型,主要是执行计算任务,响应时间很快,cpu 一直在运行,这种任务 cpu 的利用率很高,那么线程数的配置应该根据 CPU 核心数来决定,CPU 核心数=最大同时执行线程数,假如 CPU 核心数为 4,那么服务器最多能同时执行 4 个线程。过多的线程会导致上下文切换反而使得效率降低。那线程池的最大线程数可以配置为 cpu 核心数+1

如果是 IO 密集型,主要是进行 IO 操作,执行 IO 操作的时间较长,这是 cpu 出于空闲状态,导致 cpu 的利用率不高,这种情况下可以增加线程池的大小。这种情况下可以结合线程的等待时长来做判断,等待时间越高,那么线程数也相对越多。一般可以配置 cpu 核心数的 2 倍

一个公式:线程池设定最佳线程数目 = ((线程池设定的线程等待时间 + 线程 CPU 时间)/ 线程 CPU 时间 )* CPU 数目

这个公式的线程 cpu 时间是预估的程序单个线程在 cpu 上运行的时间(通常使用 loadrunner 测试大量运行次数求出平均值)

拒绝策略

AbortPolicy:直接丢弃任务,并抛出拒绝执行 RejectedExecutionException 异常信息,默认策略

CallerRunsPolicy:用调用者所在的线程来执行任务

DiscardOldestPolicy:丢弃阻塞队列中最老的一个任务,并将新任务加入

DiscardPolicy:直接丢弃任务