Java线程池整理

一、如何构建线程池?

 

我相信多数用过线程池的Java程序员都用过Executors来创建线程池,该类提供了几个静态方法,可以快速创建线程池。

如上图所示,可以创建四种类型的线程池

 

  • 固定线程数量的线程池。

  • 根据需要创建线程的线程池。

  • 执行定时任务的线程池。

  • 单个线程的线程池。

 

多数情况下,这几种类型的线程池就能满足我们的需要。但是实际上还有一个创建线程池的方法那就是手动构造线程池(ThreadPoolExecutor)

 

二、ThreadPoolExecutor

 

如果你去看一下前面提到的Executors的几个静态方法的实现,你会发现他们其实就用到了ThreadPoolExecutor,只是根据不同的场景传入了不同的参数。完整的ThreadPoolExecutor总共有七个参数 如图

 

  • corePoolSize。核心线程数

  • maximumPoolSize。最大线程数

  • keepAliveTime。线程存活时间

  • unit。 存活时间的单位

  • workQueue。 工作队列

  • threadFactory。构造线程池中线程的工厂

  • handler。任务不能被处理时的拒绝策略

 

三、工作原理(流程)

 

上面列出的 几个参数,我虽然都给了中文解释,但是如果不结合原理来描述一下他们的具体作用,有些参数我感觉还是不好理解。所以这里就把线程池工作原理和参数一起讲。

 

  • 提交任务的时候,判断当前线程池中的存活线程数量是否小于corePoolSize

  • 如果小于corePoolSize,则不管是否有线程处于空闲状态,都会新建一个线程。

  • 如果线程数量已经达到corePoolSize,则将任务扔进队列workQueue

  • 随着任务越来越多,队列可能已经满了,则需要看当前线程是否已经达到了maximumPoolSize,如果没有达到,则创建新的线程,并用它执行该任务。

  • 最坏情况,任务实在太多了,队列已经满了,而线程数量已经达到maximumPoolSize,还有新的任务来,说白了,就是已经满负荷了,任然还有任务需要执行,这个时候就会handler来处理该任务了。

 

整个工作原理就这样了,标红部分尤其重要,面试稍微深入一点,肯定就会问到这个,一定记住,是先将任务扔进队列,队列满了之后才会继续考虑创建线程。至于为什么要这样设计,可以想一想,想不通可以给我发消息。整个原理说下来,还有3个参数没有提到,这里再说明一下他们的作用

 

  • keepAliveTime。如我们所知,使用线程池的目的就是为了减少线程的创建,因为创建线程本身是比较耗资源的。由于线程本身需要占用资源,有一种情况就是,某个时候线程数量比较多,但是任务没有多少,就会出现有的线程没有活干,所以我们就可以考虑释放掉其资源,但是呢,我们又无法预知未来的任务量,所以我们就准许其空闲一段时间,如果过了这段时间都还是空闲的,那么就会释放掉其资源(就像在公司上班,可能有段时间没活干,老板可能并不会让你走,要是长时间没活干,老板可能就为了节约成本,要裁员了),这个参数就是用指定这段空闲时间的。默认情况下是有超过corePoolSize个线程时,就会用到该值, 但是也可以指定corePoolSize数量之内的线程空闲时是否释放资源(allowCoreThreadTimeout)。(就类似默认情况下,公司肯定只会裁掉非核心员工,但是实在混不走的时候,核心员工可能也会被干掉)

     

  • unit 这个参数很好理解,就是单位,就是前面keepAliveTime这个我们准许空闲的时间的单位

     

  • factory .其类型为ThreadFactory,顾名思义,就是一个创建Thread的Factory. 该接口只有一个方法,产生一个Thread。通常情况下,我们都只需要使用默认的factory就可以了,但是为了定位问题方便,我们可以为线程池创建的线程设置一个名字,这样看日志的时候就比较方便了。

 

四、RejectExecutionHandler

 

这个拒绝策略有必要拿出来单独说一下,我今天就是实现了该接口,从而满足了业务需要。

 

为什么我需要自己实现该接口,而采用Executors静态方法时,并没有让传入该参数呢?实际上Jdk本身提供了四种策略,分别是

 

  • AbortPolicy。会抛出异常

  • CallerRunnerPolicy。在调用execute的方法中执行被拒绝的任务

  • DiscardOldestPolicy。丢掉队列中最老的任务,然后重试

  • DiscardPolicy。直接丢掉该任务

 

这四种策略是ThredPoolExecutor的内部类,实现都比较简单,有兴趣的可以看一下。我今天的实现方式也很简单,实际上就是在discardPolicy的基础上增加日志记录。

 

五、其他

 

前面说了其工作原理,但是看了一下源代码,其实和描述的原理并不完全一致。主要在处理队列大小的时候,主要是对正在运行的线程数量还个判断,不能超过指定的值,当然这个值比较大,我们一般不会达到这个值,至于具体原因我也么去继续深入研究。

 

其次就是参数设置,可能需要具体业务场景,任务数量,任务执行速度来调整,并没有一个固定的值。只是记得一定要设置队列大小,不然就使用了一个无界队列,可能就是会内存爆掉。

 

实际使用场景下,还有一些可以优化的地方,比如对不同类型的任务创建不同的线程池, 比如有的线程比较耗时,有的很快,如果放在同一个线程池里面执行,可能导致队列很快就满了,本来该很快执行完的任务却一直得不到执行。

 

另外还有ThreadPoolExecutor还提供了一些hook方法,如有需要可以使用

 

  • beforeExecute()  任务执行之前调用

  • afterExecute() 任务执行之后调用

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值