如何更好的使用JAVA线程池

这篇文章结合Doug Lea大神在JDK1.5提供的JCU包,分别从线程池大小参数的设置、工作线程的创建、空闲线程的回收、阻塞队列的使用、任务拒绝策略、线程池Hook等方面来了解线程池的使用,其中涉及到一些细节包括不同参数、不同队列、不同拒绝策略的选择、产生的影响和行为、为更好的使用线程池奠定知识基础,其中值得注意的部分我用粗体标识 。

如何更好的使用JAVA线程池

文章插图
 
Doug Lea
ExecutorService基于池化的线程来执行用户提交的任务,通常可以简单的通过Executors提供的工厂方法来创建ThreadPoolExecutor实例 。
线程池解决的两个问题:1)线程池通过减少每次做任务的时候产生的性能消耗来优化执行大量的异步任务的时候的系统性能 。2)线程池还提供了限制和管理批量任务被执行的时候消耗的资源、线程的方法 。另外ThreadPoolExecutor还提供了简单的统计功能,比如当前有多少任务被执行完了 。
快速开始
为了使得线程池适合大量不同的应用上下文环境,ThreadPoolExecutor提供了很多可以配置的参数和可被用来扩展的钩子 。然而,用户还可以通过使用Executors提供的一些工厂方法来快速创建ThreadPoolExecutor实例 。比如:
  1. 使用Executors#newCachedThreadPool可以快速创建一个拥有自动回收线程功能且没有限制的线程池 。
  2. 使用Executors#newFixedThreadPool可以用来创建一个固定线程大小的线程池 。
  3. 使用Executors#newSingleThreadExecutor可以用来创建一个单线程的执行器 。
如果上面的方法创建的实例不能满足我们的需求,我们可以自己通过参数来配置,实例化一个实例 。
关于线程数大小参数设置需要知道的
ThreadPoolExecutor会根据corePoolSize和maximumPoolSize来动态调整线程池的大小:poolSize 。
当任务通过executor提交给线程池的时候,我们需要知道下面几个点:
  1. 如果这个时候当前池子中的工作线程数小于corePoolSize,则新创建一个新的工作线程来执行这个任务,不管工作线程集合中有没有线程是处于空闲状态 。
  2. 如果池子中有比corePoolSize大的但是比maximumPoolSize小的工作线程,任务会首先被尝试着放入队列,这里有两种情况需要单独说一下:
  3. a、如果任务呗成功的放入队列,则看看是否需要开启新的线程来执行任务,只有当当前工作线程数为0的时候才会创建新的线程,因为之前的线程有可能因为都处于空闲状态或因为工作结束而被移除 。
  4. b、如果放入队列失败,则才会去创建新的工作线程 。
  5. 如果corePoolSize和maximumPoolSize相同,则线程池的大小是固定的 。
  6. 通过将maximumPoolSize设置为无限大,我们可以得到一个无上限的线程池 。
  7. 除了通过构造参数设置这几个线程池参数之外我们还可以在运行时设置 。
核心线程WarmUp
默认情况下,核心工作线程值在初始的时候被创建,当新任务来到的时候被启动,但是我们可以通过重写prestartCoreThread或prestartCoreThreads方法来改变这种行为 。通常场景我们可以在应用启动的时候来WarmUp核心线程,从而达到任务过来能够立马执行的结果,使得初始任务处理的时间得到一定优化 。
定制工作线程的创建
新的线程是通过ThreadFactory来创建的,如果没有指定,默认的Executors#defaultThreadFactory将被使用,这个时候创建的线程将都属于同一个线程组,拥有同样的优先级和daemon状态 。扩展配置ThreadFactory,我们可以配置线程的名字、线程组合daemon状态 。如果调用ThreadFactory#createThread的时候失败,将返回null,executor将不会执行任何任务 。
空闲线程回收
如果当前池子中的工作线程数大于corePoolSize,如果超过这个数字的线程处于空闲的时间大于keepAliveTime,则这些线程将会被终止,这是一种减少不必要资源消耗的策略 。这个参数可以在运行时被改变,我们同样可以将这种策略应用给核心线程,我们可以通过调用allowCoreThreadTimeout来实现 。
选择合适的阻塞队列
所有的阻塞队列都可以被用来存放任务,但是使用不同的队列针对corePoolSize会表现不同的行为:
当池中工作线程数小于corePoolSize的时候,每次来任务的时候都会创建一个新的工作线程 。
当池中工作线程数大于等于corePoolSize的时候,每次任务来的时候都会首先尝试将线程放入队列,而不是直接去创建线程 。
如果放入队列失败,且当先池中线程数小于maximumPoolSize的时候,则会创建一个工作线程 。


推荐阅读