线程池

什么是线程池

合理使用线程池的好处:

线程池的实现原理

参考博客:https://blog.csdn.net/mine_song/article/details/70948223

ThreadPoolExecutor

大的实箭头表示继承(箭头尾是子类),大的虚箭头表示实现,小的实箭头表示表示关联即Executors中有ThreadPoolExecutor的实例引用。

ThreadPoolExecutor表示一个线程池,Executors表示一个线程池工厂的角色。实际上Executors创建线程池也是通过调用ThreadPoolExecutor的构造方法来实现的。

 

  • CallerRunsPolicy:只要线程池没关闭,就直接用调用者所在线程来运行任务
  • AbortPolicy:直接抛出RejectedExecutionException异常
  • DiscardPolicy:悄悄把任务放生,不做了
  • DiscardOldestPolicy:把队列里待最久的那个任务扔了,然后再调用execute()试试看能行不
  • 我们也可以实现自己的RejectedExecutionHandler接口自定义策略,比如如记录日志什么的

保存待执行任务的阻塞队列

当线程池中的核心线程数已满时,任务就要保存到队列中了。

线程池中使用的队列是BlockingQueue接口,常用的实现有如下几种:

ArrayBlockingQueue

基于数组、有界,按 FIFO(先进先出)原则对元素进行排序

LinkedBlockingQueue

基于链表,按FIFO(先进先出)排序元素,吞吐量通常要高于 ArrayBlockingQueue

Executors.newFixedThreadPool() 使用了这个队列

SynchronousQueue

不存储元素的阻塞队列,每个插入操作必须等到另一个线程调用移除操作,否则插入操作一直处于阻塞状态,吞吐量通常要高于LinkedBlockingQueue。Executors.newCachedThreadPool使用了这个队列

PriorityBlockingQueue

具有优先级的、无限阻塞队列

execute方法

ThreadPoolExecutor中的execute方法是线程池的核心调度方法:

四种种线程池

调用Executors中的5个静态方法,创建线程池。具体如下:

1.newFixedThreadPool

不招外包,有固定数量核心成员的正常互联网团队。

可以看到,FixedThreadPool 的核心线程数和最大线程数都是指定值,也就是说当线程池中的线程数超过核心线程数后,任务都会被放到阻塞队列中。

此外 keepAliveTime 为 0,也就是多余的空余线程会被立即终止(由于这里没有多余线程,这个参数也没什么意义了)。

而这里选用的阻塞队列是 LinkedBlockingQueue,使用的是默认容量 Integer.MAX_VALUE,相当于没有上限。

因此这个线程池执行任务的流程如下:

线程数少于核心线程数,也就是设置的线程数时,新建线程执行任务

线程数等于核心线程数后,将任务加入阻塞队列

由于队列容量非常大,可以一直加加加

执行完任务的线程反复去队列中取任务执行

应用:FixedThreadPool 用于负载比较重的服务器,为了资源的合理利用,需要限制当前线程数量

 

2.newSingleThreadExecutor

不招外包,只有一个核心成员的创业团队。

从参数可以看出来,SingleThreadExecutor 相当于特殊的 FixedThreadPool,它的执行流程如下:

线程池中没有线程时,新建一个线程执行任务

有一个线程以后,将任务加入阻塞队列,不停加加加

唯一的这一个线程不停地去队列里取任务执行

听起来很可怜的样子 - -。

应用:SingleThreadExecutor 用于串行执行任务的场景,每个任务必须按顺序执行,不需要并发执行。

 

3. newCachedThreadPool

全部外包,没活最多待 60 秒的外包团队。

可以看到,CachedThreadPool 没有核心线程,非核心线程数无上限,也就是全部使用外包,但是每个外包空闲的时间只有 60 秒,超过后就会被回收。

CachedThreadPool 使用的队列是 SynchronousQueue,这个队列的作用就是传递任务,并不会保存。

因此当提交任务的速度大于处理任务的速度时,每次提交一个任务,就会创建一个线程。极端情况下会创建过多的线程,耗尽 CPU 和内存资源。

它的执行流程如下:

没有核心线程,直接向 SynchronousQueue 中提交任务

如果有空闲线程,就去取出任务执行;如果没有空闲线程,就新建一个

执行完任务的线程有 60 秒生存时间,如果在这个时间内可以接到新任务,就可以继续活下去,否则就拜拜

由于空闲 60 秒的线程会被终止,长时间保持空闲的 CachedThreadPool 不会占用任何资源。

应用:CachedThreadPool 用于并发执行大量短期的小任务,或者是负载较轻的服务器。

 

4. newScheduledThreadPoolExecutor

ScheduledThreadPoolExecutor继承自ThreadPoolExecutor,最多线程数为Integer.MAX_VALUE,使用DelayedWorkQueue作为任务队列。

ScheduledThreadPoolExecutor添加任务和执行任务的机制与ThreadPoolExecutor有所不同。

ScheduledThreadPoolExecutor添加任务提供了另外两个方法:

scheduleAtFixedRate():按某种速率周期执行

scheduleWithFixedDelay():在某个延迟后执行

 

DelayQueue中封装了一个优先级队列PriorityBlockingQueue,这个队列会对队列中的ScheduledFutureTask进行排序,两个任务的执行 time 不同时,time 小的先执行;否则比较添加到队列中的顺序 sequenceNumber ,先提交的先执行

应用ScheduledThreadPoolExecutor用于需要多个后台线程执行周期任务,同时需要限制线程数量的场景。

总结

5个静态方法可以创建4种线程池:

(1)FixedThreadPool 用于负载比较重的服务器,为了资源的合理利用,需要限制当前线程数量。

(2)SingleThreadExecutor 用于串行执行任务的场景,每个任务必须按顺序执行,不需要并发执行。

(3)CachedThreadPool 用于并发执行大量短期的小任务,或者是负载较轻的服务器。

(4)ScheduledThreadPoolExecutor 用于需要多个后台线程执行周期任务,同时需要限制线程数量的场景。

两种提交任务的方法

ExecutorService提供了两种提交任务的方法:

execute():提交不需要返回值的任务

submit():提交需要返回值的任务

关闭线程池

线程池即使不执行任务也会占用一些资源,所以在我们要退出任务时最好关闭线程池。

有两个方法关闭线程池:

shutdown()

将线程池的状态设置为 SHUTDOWN,然后中断所有没有正在执行的线程

shutdownNow()

将线程池设置为 STOP,然后尝试停止所有线程,并返回等待执行任务的列表

它们的共同点是:都是通过遍历线程池中的工作线程,逐个调用Thread.interrup()来中断线程,所以一些无法响应中断的任务可能永远无法停止(比如Runnable)。

 

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值