资讯专栏INFORMATION COLUMN

ThreadPoolExecutors工作原理(一)

yuanzhanghu / 1271人阅读

摘要:当线程池执行一个任务集合时,它也会持有一些基本的统计数据,例如完成任务的数量。当命令以超过队列所能处理的平均数连续到达时,此策略允许无界线程具有增长的可能性。回收当线程池在程序中不在被引用并且不再持有线程,将会自动关闭。

概要

线程池主要解决两个问题:当执行多个异步任务时,可以实验线程池来提高性能,因为使用线程池可以减少了每个任务的调用开销,并且提供了限制和管理资源的方式,例如线程资源。当线程池执行一个任务集合时,它也会持有一些基本的统计数据,例如完成任务的数量。为了能够在广泛的环境中可用,这个类提供了很多可调整的参数和一些可扩展的钩子。然而程序员都更加喜欢使用一些工厂方法:Executors#newCachedThreadPool(无界线程池并且线程可自动回收)、Executors#newFixedThreadPool(固定大小的线程池)、Executors#newSingleThreadExecutor(单个后台线程),这些工厂已经预配置的最常用的场景,你也可以手动的按照如下的指南配置和调整这个类:

核心(corePoolSize)和最大线程池数(maximumPoolSize):
ThreadPoolExecutor可以根据核心线程数和最大线程数自动调整池的大小。

当调用方法execute(Runable)提交一个新的任务时:

如果正在运行的线程数小于corePoolSize:

创建一个新的线程处理请求,即使其他的工作线程处于空闲状态。

如果正在运行的线程数大于corePoolSize但是小于maximumPoolSize:

只有队列满时才会重新创建一个新的线程。

通过设置corePoolSize=maximumPoolSize,你可以创建一个大小固定的线程池。

通过设置maximumPoolSize为一个无穷大数值(例如Integer.MAX_VALUE),那么说明你配置的线程池可以容纳任意数量的并发任务。

一般情况下,corePoolSize和maximumPoolSize都会在创建的时候指定的,但是你们也可以通过调用setCorePoolSize()setMaximumPoolSize()来动态的调整这两个值。

按需创建:
默认情况下,核心线程只有当任务到达时才会进行创建和开启,但是可以重写方法`prestartCoreThread`或者`prestartAllCoreThreads`改变这个行为。如果你构建的线程池带有一个非空的队列,你可能需要提前开启一些线程。

创建新的线程:

新的线程是通过使用ThreadFactory来创建的,如果没有指定的话,就会使用默认的Executors#defaultThreadFactory,这个默认的工厂创建出的线程都具有相同的ThreadGroup和相同的优先级并且都不是后台线程。通过实现一个不同的线程工厂,你可以修改线程的名字、线程组、优先级、后台状态等等。如果ThreadFactory在从newThread中返回null时未能创建线程,则执行程序将继续,但可能无法执行任何任务。线程应该具有修改线程的权限。如果工作线程或者其他线程使用线程池是不具有这个权限,服务可以会被降级:配置改变可能无法及时生效,and a shutdown pool may remain in a* state in which termination is possible but not completed。

Keep-alive时间:
如果线程池有超过corePoolSize数的线程数,如果这些过量的线程空闲时间超过`keepAliveTime`将会被终止。当线程池没有被使用充分时,这种机制可以降低资源的消耗。当线程池之后又变得活跃起来,新的线程又会被创建。这个参数可以被动态的改变,使用`setKeepAliveTime(long,TimeUnit)`。通过使用`Integer.MAX_VALUE`可以有效的禁用此功能。默认情况下,只有当前的线程数大于corePoolSize,这个策略才会生效。但是方法`allowCoreThreadTimeOut(boolean)`也能够将核心线程使用这种策略,只要`keepAliveTime`非0即可。
队列:
任何`BlockingQueue`都可以用来传输和保存提交的任务,此队列的使用和线程池大小有如下的交互:

如果运行的线程数量小于corePoolSize:

Executor会创建一个新的线程而不是添加到队列中。

如果允许的线程数量大于corePoolSize:

Executor会将任务添加到队列中而不是创建一个新的线程。

如果一个请求不能添加到队列中(队列已满),如果允许的线程小于maximumPoolSize,将会创建一个新的线程,否则,该任务将会被拒绝。

队列有三种常见的策略:

直接提交: 它将任务直接提交给线程而不保存它们。在此,如果不存在可用于立即运行任务的线程,则试图把任务加入队列将失败,因此会构造一个新的线程。此策略可以避免在处理可能具有内部依赖性的请求集时出现锁。直接提交通常要求无界 maximumPoolSizes 以避免拒绝新提交的任务。当命令以超过队列所能处理的平均数连续到达时,此策略允许无界线程具有增长的可能性。 SynchronousQueue线程安全的Queue,可以存放若干任务(但当前只允许有且只有一个任务在等待),其中每个插入操作必须等待另一个线程的对应移除操作,也就是说A任务进入队列,B任务必须等A任务被移除之后才能进入队列,否则执行异常策略。你来一个我扔一个,所以说SynchronousQueue没有任何内部容量。关于SynchronousQueue:http://blog.csdn.net/yanyan19...

无界队列:使用一个没有提前预设容量的无界的队列,例如:LinkedBlockingQueue,当所有的核心线程处于繁忙时,所有新的任务都会被添加到队列中。因此如果运行线程数不超过corePoolSize,将会创建一个新的线程(maximumPoolSize这个值将不会再起作用),当每个任务完全独立于其他任务时,这可能是合适的,因此任务不能影响彼此的执行。在一个网页服务器。虽然这种排队方式可以有效地消除瞬时突发请求,但是当命令以比它们可以被处理的速度更快地平均到达时,可能会导致无限制的工作队列增长。

有界队列:有限的队列(例如,ArrayBlockingQueue)有助于防止与有限的maximumPoolSizes一起使用时的资源耗尽,但可能更难以调整和控制。 队列大小和最大池大小可以相互交换:使用大队列和小池可以最大限度地减少CPU使用率,操作系统资源和上下文切换开销,但可能导致人为的低吞吐量。 如果任务经常阻塞(例如,如果它们是I / O型操作),则系统可能能够安排时间来获得比您允许的更多的线程。 使用小队列通常需要更大的池大小,这会使CPU更繁忙,但可能会遇到不可接受的调度开销,这也会降低吞吐量。

拒绝任务:
当Executor已经被关闭时,再调用`execute(Runable)`方法添加一个任务时将会被拒绝,当Executor使用有界队列时,队列和最大线程数都已经饱和,将会调RejectedExecutionHandler#rejectedExecution(Runnable, ThreadPoolExecutor)方法。提供4种预定义的处理器:

ThreadPoolExecutor.AbortPolicy:该处理器抛出一个运行时异常RejectedExecutionException。

ThreadPoolExecutor.CallerRunsPolicy:使用调用者自己的线程运行任务。 提供了一个简单的反馈控制机制,可以减慢提交新任务的速度。

ThreadPoolExecutor.DiscardPolicy:丢弃任务。

ThreadPoolExecutor.DiscardOldestPolicy:如果executor还没有被关闭,队列头部的任务将会被丢弃,并且重新执行(可能再一次失败,但是会重复执行)

你可以定义并使用其他的实现自RejectedExecutionHandler的类。要做到这一点需要特别注意,特别是在策略仅在特定能力或排队政策下工作的情况下。

钩子方法:
这个类提供一写被protected修饰的方法:

beforeExecute(Thread, Runnable),afterExecute(Runnable, Throwable)

这些方法会在每个任务执行之前和执行之后被调用,这些可以用来操作执行环境;例如:重新初始化ThreadLocals,收集统计数据,或者添加log。另外,terminated也可以被重写当执行程序完全终止后需要执行的特殊处理。

如果钩子或者callback方法抛出异常,内部工作线程可能会失败并突然终止。

维护队列:
方法`getQueue()`运行访问工作队列以此来进行监控和调试。强烈建议不要将这种方法用于任何其他目的。当大量的排队的任务被取消时,两个提供的方法`remove()`和`purge()`可用来帮助存储回收。
回收:
当线程池在程序中不在被引用并且不再持有线程,将会自动关闭。如果你希望确保即使用户忘记调用`shutdown()`也可以回收未引用的线程池,那么必须设置适当的保持活动的时间,使用0核心线程的下限来安排未使用的线程最终死亡或设置`allowCoreThreadTimeOut(boolean)`


问题?

队列中的三种策略中,直接提交的工作原理是怎么样的?

如何扩展ThreadPoolExecutors?

线程池的工作原理是怎么样的?如何提交一个任务?如何处理一个任务?

创建一个线程的流程?

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/68153.html

相关文章

  • 【Vue原理】Watch - 白话版

    摘要:而是在初始化时,在读取了监听的数据的值之后,便立即调用一遍你设置的监听回调,然后传入刚读取的值设置了时,如何工作我们都知道有一个选项,是用来深度监听的。 写文章不容易,点个赞呗兄弟专注 Vue 源码分享,文章分为白话版和 源码版,白话版助于理解工作原理,源码版助于了解内部详情,让我们一起学习吧研究基于 Vue版本 【2.5.17】 如果你觉得排版难看,请点击 下面链接 或者 拉到 下...

    hzx 评论0 收藏0
  • 原理剖析(第 003 篇)ThreadPoolExecutor工作原理分析

    摘要:原理剖析第篇工作原理分析一大致介绍相信大家都用过线程池,对该类应该一点都不陌生了我们之所以要用到线程池,线程池主要用来解决线程生命周期开销问题和资源不足问题我们通过对多个任务重用线程以及控制线程池的数目可以有效防止资源不足的情况本章节就着 原理剖析(第 003 篇)ThreadPoolExecutor工作原理分析 - 一、大致介绍 1、相信大家都用过线程池,对该类ThreadPoolE...

    CatalpaFlat 评论0 收藏0
  • 搜索引擎的工作原理是什么

    摘要:互联网信息爆发式增长时代,要想做好就必须简单了解搜索引擎基本工作原理以及自然排名机制搜索引擎工作过程是非常复杂,冬镜在本章介绍的内容相对于真正的搜索引擎技术来说仅仅是皮毛不过对新手已经足够用了,我尽量以最容易理解的方式来讲解一搜索引擎蜘蛛搜互联网信息爆发式增长时代,要想做好SEO就必须简单了解搜索引擎基本工作原理以及自然排名机制搜索引擎工作过程是非常复杂,冬镜SEO在本章介绍的内容相对于真正...

    Tecode 评论0 收藏0
  • 原理剖析(第 004 篇)CAS工作原理分析

    摘要:原理剖析第篇工作原理分析一大致介绍关于多线程竞争锁方面,大家都知道有个和,也正是这两个东西才引申出了大量的线程安全类,锁类等功能而随着现在的硬件厂商越来越高级,在硬件层面提供大量并发原语给我们层面的开发带来了莫大的利好本章节就和大家分享分 原理剖析(第 004 篇)CAS工作原理分析 - 一、大致介绍 1、关于多线程竞争锁方面,大家都知道有个CAS和AQS,也正是这两个东西才引申出了大...

    leanote 评论0 收藏0
  • ESLint 工作原理探讨

    摘要: 彻底理解ESLint。 原文:ESLint 工作原理探讨 作者:zhangwang Fundebug经授权转载,版权归原作者所有。 ESLint 可谓是现代前端开发过程中必备的工具了。其用法简单,作用却很大,使用过程中不知曾帮我减少过多少次可能的 bug。其实仔细想想前端开发过程中的必备工具似乎也没有那么多,ESLint 做为必备之一,值得深挖,理解其工作原理。 在正式讨论原理...

    wuyangchun 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<