@[toc]分析执行人工厂方法类后,让我们看一下AbstractexecutorService,ThreadPoolExecutor的最重要的实现类。
ThreadPoolExecutor类是AbstractexeCutorService的实现类。其类的主要结构如下:
我们可以查看此类的注释:
其含义是:执行人员服务实现类,该类使用带有多个线程的池中的线程来执行提交的任务。这些线程通常使用出厂方法执行者进行配置。线程池用于解决两个不同的问题。由于每个任务的呼叫开销都减少了,因此在执行大量异步任务时,它们通常会提供改进的性能,并提供绑定和管理资源(包括线程(包括线程)方法。该资源在执行过程中消耗任务收集,每个ThreadPooleExecutor还保留了一些基本的统计信息,例如已完成任务的数量。为了在广泛的上下文中有用,此类提供许多可调节的参数和可扩展的挂钩函数。强烈建议您强烈建议您使用程序员使用执行器出厂方法的newcachedthreadpool。此方法的线程池没有边界和自动线程回收。newfixedThreadPool(固定 - 尺寸 - 尺寸线程池)和NewsellingThreadExecutor(单个背景线程)。可以为最常见的使用情况设置使用最常见的使用情况。。
核心和最大的池尺寸:ThreadPoolExecutor将根据CorePoolSize和MaximumpoolSize自动调整池的大小。请参阅GetPoolSize。当方法执行中新任务时,即使其他corepoolsize则在方法中提交新任务,即使其他其他线程也少于CorePoolsize线程。工作线程处于空闲状态,将创建一个新线程来处理请求。如果线程数量大于corepoolsize的线程数,但是小于maximummumpoolsize。它仅在队列填充时创建一个线程。通过CorePoolSize和maximumpoolsize可以作为固定尺寸的线程池创建。通过将MaximumpoolSize设置为本质上无限的Integer.max_value,线程池可以容纳任意并发任务。通常,只有在构造时才设置核心线程和最大的池大小。但是,您还可以使用setCorePoolSize和setMaxImumpoolsize动态更改。
按需施工:默认情况下,即使是核心线程也只有在新任务到达时才启动,但是可以使用prestartcorethread或prestartaltallcorethreads dynamic。- 可能需要启动的线程。
创建一个新线程:使用ThreadFactory创建一个新线程。如果未指定,则将默认方法的默认方法。该方法创建的所有线程具有相同的norm_priority Priority和non -guardian thread。通过提供不同的threadFactiry,可以更改线程的名称,线程组,优先级,优先级,优先级,以及Guardian线程状态。如果线程事件未能通过将NULL返回null来创建线程故障,则执行程序将继续,但不能执行任何任务。线程应具有modifyThread的运行时间,如果工作线程或其他工作线程。线程池的线程没有此授权,可以降级服务,并且配置更改可能不会立即生效。关闭线程池可以在可能的情况下保持在状态,但尚未完成。
保持空间时间:如果当前池中的线程数超过corepoolsize,则在空闲时间超过keepalivetime时,多余的线程将结束。请参考getKeepAlivEtime。当您不积极使用线程池时,这提供了减少资源消耗的方法。您还可以使用该参数的方法setKepalivetime调整(长,timeunit)动态调整。如果您使用long.max_value和timeunit#nanoseconds,免费线程的使用将在关闭线程池之前不会关闭。当CorePoolSize线程的数量超过很长的时间时,应用活动策略。但是,只要KeyivEtime值不是0,方法也可以使用该方法允许CoreThReadTimeOut将此超时策略应用于核心线程。
不排队。如果您正在运行corepoolsize或更多线程,则执行程序总是喜欢按请求排队而不是添加新线程。如果不能将请求放置在队列中,则会创建一个新线程。除非线程超过Maximumpoolsize,否则在这种情况下将拒绝该任务。
通常有三种排队策略:
拒绝任务:执行器关闭并且执行器的最大线程数量和工作线的容量有限时,方法执行方法提交的新任务将被拒绝。将使用recult exexeCucutionHandler的recuptexecucutionhandler#recuptexecution(可运行,threadpoolexecutor)方法。提供4预订策略:
挂钩方法:线程池类提供受保护权限之前的重写和后期的重写方法。这些方法在每个线程之前和之后都调用。这些方法可用于操作执行环境,例如,重新启动threadlocals以收集统计信息或收集统计信息或添加统计条目。此外,一旦线程完成,可以涵盖终止方法以执行需要执行的任何特殊处理。如果挂钩函数或返回方法异常,则内部工作的线程可能会失败并突然终止。
任务维护:方法GetQueue允许访问工作队列监视和调试。强烈建议不要将此方法用于任何其他目的。当取消大量排队任务时,您可以使用两种提供的方法。
最终确定:如果线程池不再在程序中引用,或者没有剩余的线程,则线程池将自动关闭。如果用户忘记呼叫关闭,如果要确保招募未解决的线程池,则必须使用0个核心线的下限设置适当的守护时间,以使未使用的线程最终消失。通过allowCoreThreadTimeout。示例:这些扩展名中的大多数涵盖了一个或多个受保护的挂钩方法,例如:以下是一个子类别。,添加了一个简单的暂停/持续功能:
在班上,关于线程池状态的评论很多:
这个想法是;主线程池的控制状态CTL是两个概念的两个概念字段的原子整数。以及指示有效螺纹数的运行状态。它用于指示它是否正在运行和关闭。为了将它们打包到int,我们将工作量限制为(2 ^ 29)-1约5亿个线程。可以使用atomiclong进行替换。并调整换档 /掩码常数。但是,在需要之前,使用int可以更快地使代码。该值可能与实际活动线程数暂时不同。例如,在请求时,线程factory无法创建线程,并且当出口线程在终止线程结束之前执行会计操作时,用户的可见池大小报告是工作集合的当前大小。Runstate提供了主要生命周期控制。值是:
这些值之间的顺序序列非常重要,可以有序地比较。Runstate随着时间的推移会增加单调,但不需要达到每个状态。您可以如下过渡:
1.2.1 CTL CTL是线程状态runstate和thread Pool workerCount上线程poolexecuter的综合字段。将这两个属性放在AtomicInteger上。CTL长度为32位。前三位数字用于存储Runstate,并且29位存储存储在WorkerCount中。因此,线程池中的最大线程数为2^29-1。代码如下:
我们可以看一下每个州的二元状况:
可以看出,通过位移操作,最高的三个位用于识别跑步状态,后者29位用于存储工作。0000,因此位移过程如下:
这两种方法可以快速计算结果。WC,这很容易理解。
上面的图片非常方便地将Runstate和WorkerCount结合起来。根据第一张图片,可以看出,跑步状态是负面和最小的。这些州的所有CTL符合以下规则:
方法runstattatelessthan和runstateTealest根据上述规则判断线程池的当前状态。可以发现,任何小于关机的状态都令人不快。这就是为什么isrunning方法的原因。此外,加上CAS的增加或减少的方法是一种增加或减少工作量的方法:比较eareAndincrementWorkerCount,CompareAnDecrementWorkerCount和DistementWorkerCount。
1.2.2工作台
Workqueue是保留任务并将其移交给工作线程的队列。我们不期望通过Workqueue判断队列是否为空。我们仅使用workqueue.sempty方法来确定队列是否为空。。这个工作场所取决于构造函数。
1.2.3工人
线程池中所有工作线程的集合。您在拜访工人时需要获得Mainlock。该工人是标签。
1.2.4主锁和终止
调用此锁定时,您需要锁定工人的位置并执行相关记录。尽管我们使用某种方法并选择了它,但事实证明,使用锁的方法通常是可取的。原因之一是它可以序列化Interruptidleworkers。这可以避免不必要的中断风暴。退出线程将中断哪些尚未同时中断的线程也简化了一些相关的数据。就像maximumpoolsize一样。我们还保留了关闭和shutdownnow上的mainlock,以确保在检查中断和实际中断权限时稳定稳定。是支持等待的等待条件。
1.2.5其他成员变量
同类中还有其他一些成员变量,如下所示:|可变名称|类型|描述|| - | - ||largetPoolsize |int |线程池的大小,您需要获得Mainlock ||已完成的任务|长|终止工作线程后,完成任务的计数器将更新。它还需要获得主锁||线程factory |挥发性螺纹fact需要在构造函数|中传递工厂方法的工厂方法。|处理程序|volatile recultdexexececuctionhandler。线程池饱和或关闭时,如果有任务,请致电||keepalivetime |挥发性长|工人在NAN秒内等待超时。当前它比CorePoolSize或允许的coreThreadTimeout大时,该线程被称为本超时,否则将等待新工作者||AlowCoreThreadTimeOut |挥发性布尔|如果是为false,即使是空闲的,核心线程也会保留该活动,否则核心线程将使用它。保存时间正在等待超时。默认值为false ||CorePoolSize |挥发性int |核心池的大小是维持生存的工人的最小数量,除非设置了允许coreThreadTimeOut。在这种情况下,最小值为0 |MAVIETUMPSIZESEE CHECKSHUTDOWNACCESS要求呼叫者有权中断集中在工作程序中的线程,由线程控制。中断,取决于螺纹group.checkaccess.relying在SecurityManager.checkaccess.checkaccess.checkaccess.checkaccess.ly上,只是在这些检查中通过后,我尝试执行关闭。线程的所有实际调用。中断将忽略SecurityExceptions。这意味着中断会以无声的方式失败。除非SecurityManager的策略不一致。建议使用intruptidleworkers的其他用途, 实际上中断的失败只会延迟对配置变化的响应,因此不会进行特殊处理。|
在ThreadPoolExecutor中,内部类有两种类型,一个是执行线程的工作者,另一个是拒绝该策略。
工人类继承了抽象的Quuessynchronizer。
代码如下:
其注释的想法是:主要维护操作任务的线程中断控制的状态以及各种情况的记录。该类继承了抽象的Synchronizer,以简化获取的锁定并围绕每个任务发布。这可以防止中断。这可以防止中断。这些中断旨在唤醒工作线程等待任务,而不是中断正在运行的任务。我们已经实现了一个简单的插入锁,而不是使用reentrantlock,因为我们不希望工作线获得工作线调用池控制方法(例如setCorepoolsize..in添加)时,锁定线程真正开始运行之前的抑制性中断。我们将锁定初始化状态设置为负值并在启动时将其删除。AQ并实现可运行的接口。它使用firstTask保存通过任务。线程是使用threadFactory创建的线程。该线程是通过处理任务来处理的。调用构造函数时,将任务传递到任务中,然后通过getThreadFactory()创建新线程。newthread(this)。启动时,这个工作对象将调用其运行方法,因为工作人员实现了可运行的接口,它本身也是线程。为什么使用继承aqs而不是使用rentrantlock?关键是不允许重复tryAcquire方法。Reentrantlock允许重新输入。在获得锁定方法后,正在执行当前线程。Worker继承的AQS,因此它也是锁定的锁。在执行过程中。任务,锁将不会被释放。使用以确保任务的正常执行。因此,任务在任务操作过程中不能中断。如果工作不是独家锁定或闲置,则 这意味着该工人无法处理任务并可以中断。线程池在执行关机和TryTerminate时会中断空闲线程。InterRuptIdixedleWorker方法使用Trylock方法来确定线程池中的线程是否是空闲的。
在ThreadPoolExecutor中,主要有四种支持的拒绝策略,所有这些策略都是通过内部类提供的。它们是Callerrunspolicy,Abortpolicy,doverdpolicy,doverdpolicy,doverDoverOldestPolicy。这些类实现了recultexecutionHandler界面。
2.2.1 Callerrunspolice
除非关闭当前线程池,否则这种拒绝策略使用线程执行执行执行执行任务。该拒绝策略不会在正常情况下导致任务失败,这可以有效地降低生产速度。用户根据场景进行选择。。
2.2.2流产
这种拒绝策略将拒绝执行任务并直接提出异常。
2.2.3丢弃
拒绝策略将丢弃当前的任务,而无能为力。
2.2.4丢弃
这种拒绝策略在队列中丢弃了旧任务,然后将当前任务添加到队列中,但是这种拒绝策略不能保证可以执行当前任务,或者可以继续被后续任务丢弃。
ThreadPoolExecutor提供了4个构造函数,为7个可以在前面变量的成员变量分配值。
实际上,只有6个变量与系统相对应。这是因为keepalivetime和单元最终被转换为纳米 - 秒单位。变量|描述|| - | - ||CorePoolSize |线程池居民线程编号,如果设置了允许的读取时间,则居民线程将在一定时间段内变为0,范围0 <= corepoolSize <= maximumumpoolsize |maximumpoolsize |线程池允许的最大线程数,范围0 <maximumpoolsize <=(2^29-1)||keepalivetime |当线程数大于核心线程CorePoolsize时,这些多余的线程将是当前任务终止后最长的线程。|单位|keepalivetime单元,最终在系统中生效||Workqueue |任务队列,取决于构造函数的简介||线程factory |生成线程的工厂方法||处理程序|拒绝策略,如果任务达到任务队列,则长度将触发拒绝策略,拒绝策略有四种类型|
实际上,使用了3.1中的七个参数的构造函数,但是默认opecutors.defaultthreadfactory()用作线程生成的出厂方法。此方法实际上是在执行者中。
该方法将采用默认拒绝策略:
默认拒绝策略在以前的变量和常数中解释了:
实际上,采用了丢弃当前任务的策略。
那么,自然而然地可以根据上述两个构造函数获得:
DefaultThreadFactory和Defaulthandler均均为默认值。因此,当我们手动新的ThreadPoolExecutor时,经常使用此结构。
实际上,在理解了先前的成员变量,评论和构造函数之后,线程poolexecutor的基本结构非常清楚。在个人上,我认为这比ConcurrentHashMap更简单。
所有线程都是从WorkersPool存储的数据结构。正常启动,此数据结构开始工作。当外部线程添加到可运行的任务中以提交Sumbit方法。这次,提交方法中有三个选项:
以上7点是ThreadPoolExecutor的核心。这也是在访谈中经常询问的一部分,以与第一部分的第一部分的状态进行,每个状态之间的切换如下所示:
在了解线程池工作的基本原理后,分析了一些常见的线程池方法。
上述过程可以由流程图表示如下:
在此方法之后,我们可以看到,实际上,仅在将队列添加和删除到工人池时才使用Mainlock。在添加或删除之前,请在此锁定库中进行。此外,本文开始定义标签语句重试:应注意的是,当前周期将结束,重新启动重试。Bream将跳出重试。
我们在前面的静态内部类中分析了工人。那么工人在开始后如何执行?
实际上,此行人方法被调用。
让我们看一下上述线程退出时执行的方法
该方法在线程退出时触发,尝试确定是否可以终止线程池
从相同的方法可以看出,在工人操作时使用mainlock,在添加和删除工人时需要锁定。此外,当调用eawattermiation()方法时,iDle Worker的等待操作也使用锁定锁的条件变量。
最后,分析另一个关键方法。GEGTASK,这是在上一个工作中获得任务的方法。该方法非常重要。
实际上,可以发现该方法的代码是,要使用阻止队列的原因是,在此方法中,如果使用keepalive的时间,则此方法将使用拉动(超时)方法来阻止当前调用线程这样,您可以阻止每个工人和getTask.cous。
打开核心线程超时或当前线程数大于核心线程。
如果5.7方法进入time_waiting状态。然后,如果需要及时使用它,则需要中断这些阻止线程。
当通话中断时,您需要返回两层。一层是主锁,另一层是线程的AQS锁。如果被占用,则不会中断线程。这样,您可以理解,刚刚创建的工人不会被中断,并且工作中的线程不会被打扰。只有等待状态中的工人才会被中断。
本文分析了threadpoolexecutor thread pool的源代码。ComurrentHashMap代码,该代码并不是特别复杂。
工人本身继承了AQ,然后实现了可运行的界面。Worker将继续从任务队列WorkQueue获得任务。大于核心线程CorePoolsize,将触发keepaliveTime的阻塞。可以将阻塞的线程视为免费线程。这些线程被阻止队列阻止,这就是线程池必须使用阻止封锁队列的原因。,它的作用是,当大多数线程休眠时,线程池中的活动线程降低到小于队列核心线程的数量。可在关闭或调整线程池时应用。AQS的目的是获取AQS锁定当中断以及新创建的正常操作中的工人和工人将导致无法获得锁,并且不会被中断。ReentrantlocakMainlock的作用是工人的操作要求此锁定锁才能增加或中断。条件的唯一作用此锁上的可变终止是在Try端到的。最后,应注意的是,线程池使用大量CAS操作。最重要的是要将线程的状态与工人合并到Aotmicinteger对象。通过操作,最高的三个位识别状态。因此,工人的大小为2^29-1。此操作的操作也是我们值得从hashmap中的位操作操作的地方,当您查看代码时,您会认为代码可以像这样编写,以提高设计功能。
最关键的部分:
原始:https://juejin.cn/post/710339310868283079