线程池工作流程、工作原理 工厂流水线、开公司打比方

 

初始化一个线程池,就是初始化一条工厂流水线;或者新建一个公司;

开始创业

核心线程个数就像是公司元老个数;

最大线程个数就像公司总相关员工;

任务队列就像是公司项目排期表;

treadFactory 创建线程的工厂         自定义员工招聘&入职流程

拒绝策略  任务队列满,并且线程数达到最大线程数,就像项目排期满了,公司员工也都在忙,就会采取相关的拒绝策略()

存活时间    如果当前公司人数比核心元老多   并且处在闲置状态    没有项目排期任务     这些员工在公司内的存活时间

存活时间单元    时间单位

开始创业  新建一个公司

1.通过execute方法提交任务时    就像是一个项目立项提交项目任务,当线程池中的线程数小于corePoolSize时,新提交的任务将通过创建一个新线程来执行,即使此时线程池中存在空闲线程。

当核心元老员工没招满时,即使存在空闲元老,也要继续招聘元老;新任务不排期,直接让元老立马干。

2.通过execute方法提交任务时,当线程池中线程数量达到corePoolSize时,新提交的任务将被放入workQueue中,等待线程池中线程调度执行。

有任务时,如果核心元老招满了,任务就会放在人任务排期表里面,等待员工来执行;

3.通过execute方法提交任务时,当workQueue已存满,且maximumPoolSize大于corePoolSize时,新提交的任务将通过创建新线程执行。

任务排期表满了,而且总员工人数大于核心元老数量时,新的任务将通过招聘外包员工完成任务,不排期

4.当线程池中的线程执行完任务空闲时,会尝试从workQueue中取头结点任务执行。

当员工执行完任务,会再次从任务排期表里面 取早期项目任务执行

5.通过execute方法提交任务,当线程池中线程数达到maxmumPoolSize,并且workQueue也存满时,新提交的任务由RejectedExecutionHandler执行拒绝操作。

有新任务时,如果总员工人数满了,任务排期表也满了,新的任务就会通过公司评估部门,执行对应的拒绝任务操作

6.当线程池中线程数超过corePoolSize,并且未配置allowCoreThreadTimeOut=true,空闲时间超过keepAliveTime的线程会被销毁,保持线程池中线程数为corePoolSize。

如果配置了允许裁员元老       当公司员工数量超过元老规定数量,且员工或元老超过了空闲时间,就会裁员任何人;

如果没配置,  当公司员工数量超过元老规定数量,且员工超过了空闲时间,就会裁员普通员工;

在《阿里巴巴java开发手册》中写到,线程池不允许使用Executors 去创建,而是通过 ThreadPoolExecutor 的方式。
Executors 返回的线程池对象的弊端如下:

1)FixedThreadPool 和 SingleThreadPool:
允许的请求队列长度为 Integer.MAX_VALUE,可能会堆积大量的请求,从而导致 OOM。
2)CachedThreadPool 和 ScheduledThreadPool:
允许的创建线程数量为 Integer.MAX_VALUE,可能会创建大量的线程,从而导致 OOM。
那ThreadPoolExecutor类如何使用。

使用Executors创建公司的坏处,FixedThreadPool 和 SingleThreadPool: 这两种任务排期表,会太多任务,导致超出公司物力成本;

CachedThreadPool 和 ScheduledThreadPool:   这两种公司会招聘大量员工,超出公司容纳的人力成本;

任务队列

1.1 直接提交的任务队列(SynchronousQueue)     直接口头立马执行的任务排期表

(1)      SynchronousQueue没有容量。  这个排期表没有容量  任务直接不排期

(2)      提交的任务不会被真实的保存在队列中,而总是将新任务提交给线程执行。如果没有空闲的线程,则尝试创建新的线程。如果线程数大于最大值maximumPoolSize,则执行拒绝策略。

这个任务排期表,实际上一有任务就直接让员工立即执行,如果没有员工有空,就招人,如果超过公司限制就直接拒绝;

1.2 有界的任务队列(ArrayBlockingQueue)       有任务数量限制的数组一样的连续时间的任务排期表

(1)      创建队列时,指定队列的最大容量。  任务排期时,指定任务排期表的最大任务数

(2)      若有新的任务要执行,如果线程池中的线程数小于corePoolSize,则会优先创建新的线程。若大于corePoolSize,则会将新任务加入到等待队列中。

如果有新的任务执行,如果公司员工数量小于元老数量,那就招人;如果大于,那就把任务加到任务排期表里面。

(3)      若等待队列已满,无法加入。如果总线程数不大于线程数最大值maximumPoolSize,则创建新的线程执行任务。若大于maximumPoolSize,则执行拒绝策略。

如果任务排期表满了,如果当前总员工数量不超过总员工规定数量,那就招人,如果大于,那就拒绝

1.3 无界的任务队列(LinkedBlockingQueue)   没有任务数量限制的链表一样的,只是按跳跃时间的项目任务排期表

(1)      与有界队列相比,除非系统资源耗尽,否则不存在任务入队失败的情况。  

除非公司成本限制,不然不存在任务排期失败

(2)      若有新的任务要执行,如果线程池中的线程数小于corePoolSize,线程池会创建新的线程。若大于corePoolSize,此时又没有空闲的线程资源,则任务直接进入等待队列。

如果有新的任务,如果元老没招满,招元老;如果招满了元老,但是元老都在忙,直接进入任务排期表

(3)      当线程池中的线程数达到corePoolSize后,线程池不会创建新的线程。

招满元老就不招普通员工了

(4)      若任务创建和处理的速度差异很大,无界队列将保持快速增长,直到耗尽系统内存。

如果任务排期和任务完成速度差很多,就会一直给任务排期,直到耗尽公司成本

(5)     使用无界队列将导致在所有 corePoolSize 线程都忙时,新任务在队列中等待。这样,创建的线程就不会超过 corePoolSize(因此,maximumPoolSize 的值也就无效了)。当每个任务完全独立于其他任务,即任务执行互不影响时,适合于使用无界队列;例如,在 Web 页服务器中。这种排队可用于处理瞬态突发请求,当命令以超过队列所能处理的平均数连续到达时,此策略允许无界线程具有增长的可能性。
使用无界任务排期表,导致元老们在忙的时候,新任务一直等待(没人记起),这样招的员工就不会超过元老规定数量,总员工数量也失效。

当每个任务互相不影响的时候,适合用无界任务排期表,比如高并发情况下,比如中午饭馆吃饭,很多人点单

1.4 优先任务队列(PriorityBlockingQueue)

(1)      带有执行优先级的队列。是一个特殊的无界队列。

(2)      ArrayBlockingQueue和LinkedBlockingQueue都是按照先进先出算法来处理任务。而PriorityBlockingQueue可根据任务自身的优先级顺序先后执行(总是确保高优先级的任务先执行)。

 

线程池工作原理

关于线程池的工作原理,我用下面的7幅图来展示。 1.通过execute方法提交任务时,当线程池中的线程数小于corePoolSize时,新提交的任务将通过创建一个新线程来执行,即使此时线程池中存在空闲线程。

2.通过execute方法提交任务时,当线程池中线程数量达到corePoolSize时,新提交的任务将被放入workQueue中,等待线程池中线程调度执行。

3.通过execute方法提交任务时,当workQueue已存满,且maximumPoolSize大于corePoolSize时,新提交的任务将通过创建新线程执行。

4.当线程池中的线程执行完任务空闲时,会尝试从workQueue中取头结点任务执行。

5.通过execute方法提交任务,当线程池中线程数达到maxmumPoolSize,并且workQueue也存满时,新提交的任务由RejectedExecutionHandler执行拒绝操作。

6.当线程池中线程数超过corePoolSize,并且未配置allowCoreThreadTimeOut=true,空闲时间超过keepAliveTime的线程会被销毁,保持线程池中线程数为corePoolSize。

注意:上图表达的是销毁空闲线程,保持线程数为corePoolSize,不是销毁corePoolSize中的线程。

7.当设置allowCoreThreadTimeOut=true时,任何空闲时间超过keepAliveTime的线程都会被销毁。


 

 

ThreadPoolExecutor定义了七大核心属性,这些属性是线程池实现的基石。

  • corePoolSize(int):核心线程数量。默认情况下,在创建了线程池后,线程池中的线程数为0,当有任务来之后,就会创建一个线程去执行任务,当线程池中的线程数目达到corePoolSize后,就会把到达的任务放到任务队列当中。线程池将长期保证这些线程处于存活状态,即使线程已经处于闲置状态。除非配置了allowCoreThreadTimeOut=true,核心线程数的线程也将不再保证长期存活于线程池内,在空闲时间超过keepAliveTime后被销毁。
  • workQueue:阻塞队列,存放等待执行的任务,线程从workQueue中取任务,若无任务将阻塞等待。当线程池中线程数量达到corePoolSize后,就会把新任务放到该队列当中。JDK提供了四个可直接使用的队列实现,分别是:基于数组的有界队列ArrayBlockingQueue、基于链表的无界队列LinkedBlockingQueue、只有一个元素的同步队列SynchronousQueue、优先级队列PriorityBlockingQueue。在实际使用时一定要设置队列长度。
  • maximumPoolSize(int):线程池内的最大线程数量,线程池内维护的线程不得超过该数量,大于核心线程数量小于最大线程数量的线程将在空闲时间超过keepAliveTime后被销毁。当阻塞队列存满后,将会创建新线程执行任务,线程的数量不会大于maximumPoolSize。
  • keepAliveTime(long):线程存活时间,若线程数超过了corePoolSize,线程闲置时间超过了存活时间,该线程将被销毁。除非配置了allowCoreThreadTimeOut=true,核心线程数的线程也将不再保证长期存活于线程池内,在空闲时间超过keepAliveTime后被销毁。
  • TimeUnit unit:线程存活时间的单位,例如TimeUnit.SECONDS表示秒。
  • RejectedExecutionHandler:拒绝策略,当任务队列存满并且线程池个数达到maximunPoolSize后采取的策略。ThreadPoolExecutor中提供了四种拒绝策略,分别是:抛RejectedExecutionException异常的AbortPolicy(如果不指定的默认策略)、使用调用者所在线程来运行任务CallerRunsPolicy、丢弃一个等待执行的任务,然后尝试执行当前任务DiscardOldestPolicy、不动声色的丢弃并且不抛异常DiscardPolicy。项目中如果为了更多的用户体验,可以自定义拒绝策略。
  • threadFactory:创建线程的工厂,虽说JDK提供了线程工厂的默认实现DefaultThreadFactory,但还是建议自定义实现最好,这样可以自定义线程创建的过程,例如线程分组、自定义线程名称等。
  •  

一般我们使用类的构造方法创建它的对象,ThreadPoolExecutor提供了四个构造方法。

可以看到前三个方法最终都调用了最后一个、参数列表最长的那个方法,在这个方法中给七个属性赋值。创建线程池对象,强烈建议通过使用ThreadPoolExecutor的构造方法创建,不要使用Executors,至于建议的理由上文中也有说过,这里再引用阿里《Java开发手册》中的一段描述。

 

posted @ 2024-02-13 21:49  予真  阅读(35)  评论(0编辑  收藏  举报