更好的使用JAVA线程池


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

Doug Lea多线程

ExecutorService基于池化的线程来执行用户提交的任务,一般能够简单的经过Executors提供的工厂方法来建立ThreadPoolExecutor实例。
异步

线程池解决的两个问题:1)线程池经过减小每次作任务的时候产生的性能消耗来优化执行大量的异步任务的时候的系统性能。2)线程池还提供了限制和管理批量任务被执行的时候消耗的资源、线程的方法。另外ThreadPoolExecutor还提供了简单的统计功能,好比当前有多少任务被执行完了。性能


快速开始优化

为了使得线程池适合大量不一样的应用上下文环境,ThreadPoolExecutor提供了不少能够配置的参数和可被用来扩展的钩子。然而,用户还能够经过使用Executors提供的一些工厂方法来快速建立ThreadPoolExecutor实例。好比:spa

  1. 使用Executors#newCachedThreadPool能够快速建立一个拥有自动回收线程功能且没有限制的线程池。操作系统

  2. 使用Executors#newFixedThreadPool能够用来建立一个固定线程大小的线程池。线程

  3. 使用Executors#newSingleThreadExecutor能够用来建立一个单线程的执行器。debug

若是上面的方法建立的实例不能知足咱们的需求,咱们能够本身经过参数来配置,实例化一个实例。rest


关于线程数大小参数设置须要知道的

ThreadPoolExecutor会根据corePoolSize和maximumPoolSize来动态调整线程池的大小:poolSize。

当任务经过executor提交给线程池的时候,咱们须要知道下面几个点:

  1. 若是这个时候当前池子中的工做线程数小于corePoolSize,则新建立一个新的工做线程来执行这个任务,无论工做线程集合中有没有线程是处于空闲状态。

  2. 若是池子中有比corePoolSize大的可是比maximumPoolSize小的工做线程,任务会首先被尝试着放入队列,这里有两种状况须要单独说一下:

    a、若是任务呗成功的放入队列,则看看是否须要开启新的线程来执行任务,只有当当前工做线程数为0的时候才会建立新的线程,由于以前的线程有可能由于都处于空闲状态或由于工做结束而被移除。

    b、若是放入队列失败,则才会去建立新的工做线程。

  3. 若是corePoolSize和maximumPoolSize相同,则线程池的大小是固定的。

  4. 经过将maximumPoolSize设置为无限大,咱们能够获得一个无上限的线程池。

  5. 除了经过构造参数设置这几个线程池参数以外咱们还能够在运行时设置。


核心线程WarmUp

默认状况下,核心工做线程值在初始的时候被建立,当新任务来到的时候被启动,可是咱们能够经过重写prestartCoreThread或prestartCoreThreads方法来改变这种行为。一般场景咱们能够在应用启动的时候来WarmUp核心线程,从而达到任务过来可以立马执行的结果,使得初始任务处理的时间获得必定优化。


定制工做线程的建立

新的线程是经过ThreadFactory来建立的,若是没有指定,默认的Executors#defaultThreadFactory将被使用,这个时候建立的线程将都属于同一个线程组,拥有一样的优先级和daemon状态。扩展配置ThreadFactory,咱们能够配置线程的名字、线程组合daemon状态。若是调用ThreadFactory#createThread的时候失败,将返回null,executor将不会执行任何任务。


空闲线程回收

若是当前池子中的工做线程数大于corePoolSize,若是超过这个数字的线程处于空闲的时间大于keepAliveTime,则这些线程将会被终止,这是一种减小没必要要资源消耗的策略。这个参数能够在运行时被改变,咱们一样能够将这种策略应用给核心线程,咱们能够经过调用allowCoreThreadTimeout来实现。


选择合适的阻塞队列

全部的阻塞队列均可以被用来存听任务,可是使用不一样的队列针对corePoolSize会表现不一样的行为:

当池中工做线程数小于corePoolSize的时候,每次来任务的时候都会建立一个新的工做线程。

当池中工做线程数大于等于corePoolSize的时候,每次任务来的时候都会首先尝试将线程放入队列,而不是直接去建立线程。

若是放入队列失败,且当先池中线程数小于maximumPoolSize的时候,则会建立一个工做线程。

下面主要是不一样队列策略表现:

直接递交:一种比较好的默认选择是使用SynchronousQueue,这种策略会将提交的任务直接传送给工做线程,而不持有。若是当前没有工做线程来处理,即任务放入队列失败,则根据线程池的实现,会引起新的工做线程建立,所以新提交的任务会被处理。这种策略在当提交的一批任务之间有依赖关系的时候避免了锁竞争消耗。值得一提的是,这种策略最好是配合unbounded线程数来使用,从而避免任务被拒绝。同时咱们必需要考虑到一种场景,当任务到来的速度大于任务处理的速度,将会引发无限制的线程数不断的增长。

无界队列:使用无界队列如LinkedBlockingQueue没有指定最大容量的时候,将会引发当核心线程都在忙的时候,新的任务被放在队列上,所以,永远不会有大于corePoolSize的线程被建立,所以maximumPoolSize参数将失效。这种策略比较适合全部的任务都不相互依赖,独立执行。举个例子,如网页服务器中,每一个线程独立处理请求。可是当任务处理速度小于任务进入速度的时候会引发队列的无限膨胀。

有界队列:有界队列如ArrayBlockingQueue帮助限制资源的消耗,可是不容易控制。队列长度和maximumPoolSize这两个值会相互影响,使用大的队列和小maximumPoolSize会减小CPU的使用、操做系统资源、上下文切换的消耗,可是会下降吞吐量,若是任务被频繁的阻塞如IO线程,系统其实能够调度更多的线程。使用小的队列一般须要大maximumPoolSize,从而使得CPU更忙一些,可是又会增长下降吞吐量的线程调度的消耗。总结一下是IO密集型能够考虑多些线程来平衡CPU的使用,CPU密集型能够考虑少些线程减小线程调度的消耗。


选择适合的拒绝策略

当新的任务到来的而线程池被关闭的时候,或线程数和队列已经达到上限的时候,咱们须要去作一个决定,怎么拒绝这些任务。下面介绍一下经常使用的策略:

ThreadPoolExecutor#AbortPolicy:这个策略直接抛出RejectedExecutionException异常。

ThreadPoolExecutor#CallerRunsPolicy:这个策略将会使用Caller线程来执行这个任务,这是一种feedback策略,能够下降任务提交的速度。

ThreadPoolExecutor#DiscardPolicy:这个策略将会直接丢弃任务。

ThreadPoolExecutor#DiscardOldestPolicy:这个策略将会把任务队列头部的任务丢弃,而后从新尝试执行,若是仍是失败则继续实施策略。

除了上面的几种策略,咱们也能够经过实现RejectedExecutionHandler来实现本身的策略。


利用Hook嵌入你的行为

ThreadPoolExecutor提供了protected类型能够被覆盖的钩子方法,容许用户在任务执行以前会执行以后作一些事情。咱们能够经过它来实现好比初始化ThreadLocal、收集统计信息、如记录日志等操做。这类Hook如beforeExecute和afterExecute。另外还有一个Hook能够用来在任务被执行完的时候让用户插入逻辑,如rerminated。

若是hook方法执行失败,则内部的工做线程的执行将会失败或被中断。


可访问的队列

getQueue方法能够用来访问queue队列以进行一些统计或者debug工做,咱们不建议用做其余用途。同时remove方法和purge方法能够用来将任务从队列中移除。


关闭线程池

当线程池不在被引用而且工做线程数为0的时候,线程池将被终止。咱们也能够调用shutdown来手动终止线程池。若是咱们忘记调用shutdown,为了让线程资源被释放,咱们还可使用keepAliveTime和allowCoreThreadTimeOut来达到目的。


写在最后

JAVA自己提供的API已经可让咱们快速的进行基于线程池的多线程开发,可是咱们必需要为咱们写的代码负责,每个参数的设置和策略的选择跟不一样应用场景有绝对的关系。然而对于不一样参数和不一样策略的选择并非一件容易的事情,咱们必需要先回答一些基础问题:每建立一个线程,操做系统为咱们作了哪些事情,这个线程的操做系统资源消耗主要在哪部分?假如个人应用场景是IO密集型的,那么我须要更多的线程仍是更少的线程?假如咱们的CPU操做和IO操做大概各占一半的话咱们又须要如何选择?等等一些列问题。我认为、多线程开发是一件很容易的事情也是一件很不容易的事情。:)


参考文档《JDK1.5》 by Dong Lea

相关文章
相关标签/搜索