线程池工作原理
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了线程池工作原理相关的知识,希望对你有一定的参考价值。
参考技术A 管理线程,当线程执行完当前任务,不会死掉而是 会从队列里面取1.降低系统资源消耗。通过复用已存在的线程,降低线程创建和销毁造成的消耗;
2.提高响应速度。当有任务到达时,无需等待新线程的创建便能立即执行;
3.提高线程的可管理性。线程是稀缺资源,如果无限制的创建,不仅会消耗大量系统资源,还会降低系统的稳定性,使用线程池可以进行对线程进行统一的分配、调优和监控。
本文主要是围绕 ThreadPoolExecutor(线程池框架的核心类)的构造方法参数 展开:
1.corePoolSize
线程池中的核心线程数。当提交一个任务时,线程池创建一个新线程执行任务,直到当前线程数等于corePoolSize;如果当前线程数为corePoolSize,继续提交的任务被保存到阻塞队列中,等待被执行。
2.maximumPoolSize
额外最大线程数。上面说到任务数足够多,且使用的是有界队列,如果当前阻塞队列满了,且继续提交任务,则创建新的线程执行任务,首先从队列里面取,如果队列里面的消息执行完毕,等下一定时间,额外线程自动销毁。
3.keepAliveTime
线程空闲时的存活时间。默认情况下,可以理解成额外最大线程数没活干了,额外线程线程空闲的时间达到keepAliveTime,则会终止,直到线程池中的线程数不超过corePoolSize。但是如果调用了allowCoreThreadTimeOut(boolean)方法,keepAliveTime参数也会起作用,直到线程池中的线程数为0。
4.unit
keepAliveTime参数的时间单位。
5.workQueue
任务缓存队列,用来存放等待执行的任务。如果当前线程数为corePoolSize,继续提交的任务就会被保存到任务缓存队列中,等待被执行。
一般来说,这里的BlockingQueue有以下三种选择:
* SynchronousQueue:一个不存储元素的阻塞队列,每个插入操作必须等到另一个线程调用移除操作,否则插入操作一直处于阻塞状态。因此,如果线程池中始终没有空闲线程(任务提交的平均速度快于被处理的速度),可能出现无限制的线程增长。
* LinkedBlockingQueue:基于链表结构的阻塞队列,如果不设置初始化容量,其容量为Integer.MAX_VALUE,即为无界队列。因此,如果线程池中线程数达到了corePoolSize,且始终没有空闲线程(任务提交的平均速度快于被处理的速度),任务缓存队列可能出现无限制的增长。
* ArrayBlockingQueue:基于数组结构的有界阻塞队列,按FIFO排序任务。
6.threadFactory
线程工厂,创建新线程时使用的线程工厂。
7.handler
任务拒绝策略,当阻塞队列满了,且线程池中的线程数达到maximumPoolSize,如果继续提交任务,就会采取任务拒绝策略处理该任务,线程池提供了4种任务拒绝策略:
* AbortPolicy :丢弃任务并抛出RejectedExecutionException异常,默认策略;
* CallerRunsPolicy :由调用execute方法的线程执行该任务;
* DiscardPolicy :丢弃任务,但是不抛出异常;
* DiscardOldestPolicy :丢弃阻塞队列最前面的任务,然后重新尝试执行任务(重复此过程)。
* 当然也可以根据应用场景实现 RejectedExecutionHandler 接口,自定义饱和策略,如记录日志或持久化存储不能处理的任务。
总结下上诉参数:假设corePoolSize为10 ,maximumPoolSize为10,线程空闲时的存活时间为60s,队列采用的是有界队列ArrayBlockingQueue 设置阈值200,使 用拒绝策略 , 当前2000个任务提交过来 流程如下图
参数案例描述:
当前2000笔 任务进来,10个核心线程去处理,剩下的1990任务队列里面放200个。剩下的1790个任务。队列塞满会去创建10个额外线程和核心线程一起去 去执行剩下的1780个任务。 当还有剩下任务处理不了就会触发任务拒绝策略 。
当前220笔 任务进来,10个核心线程去处理,剩下的210任务队列里面放200个。剩下的10个任务。队列塞满会去创建10个额外线程 去执行队列放不下的任务。 当额外线程和核心线程处理完队列里面的队列。没有任务可执行时,额外线程会等待我们设置的keepAliveTime,还是没有任务的情况下,就会被回收了 。
以上是绝对理想的状况下。
由参数可知 核心线程 和额外线程值是相同的,额外线程被回收时间是0,采用的是无界队列。默认采用的拒绝策略为 AbortPolicy。分析得 核心线程和额外线程处理不过来得情况,会一直往队列里面放任务。
可能存在的问题:队列过大 导致内存溢出 OOM
当任务量足够大,超过队列。交由额外线程处理。就会创建过多线程 。
可能存在问题:特殊场景下,线程过多可能会导致系统奔溃。cpu负载过高。
1.具体解决方案 根据业务系统而定:
华瑞批量查证举例:定时任务CZJZRW001每隔2min 轮询一次 会从业务表verifycationTask 中 查询出待处理和处理中的状态的任务 根据表中的查证类型 分流到具体的 反欺诈异步查证 ,还款查证, 充值查证,贷款查证 。 具体查证根据处理结果更新verifycationTask表查证状态。处理成功 或者失败的定时任务无法再次轮询。这样就不需要考虑以上场景。使用线程池的情况下核心线程,额外线程处理不过来且队列已满使用DiscardPolicy拒绝不抛异常策略 ,即可满足该业务场景。类结构如下图
2.思路
可以实现 RejectedExecutionHandler接口 自定义拒绝策略 将被拒绝的任务信息缓存到磁盘,等待线程池负载较低 从磁盘读取重新提交到任务里面去执行
线程池的工作原理阅读总结
线程池使用了一种池化技术,和很多其他池化技术一样,都是为了更高效的利用资源,例如链接池,内存池等等
线程池一共有五种状态,运行状态,待关闭状态,停止状态,整理状态,终止状态,一个线程池的核心参数有很多,每个参数都有着特殊的作用,各个参数聚合在一起 后将完成整个线程池的完整工作,每一个工作线程中都维持着一个Thread,线程池的重点之一就是控制线程资源合理高效的使用,所以必须控制工作线程的个数,所以要保存当前线程中的工作线程个数。
线程池设计了两个变量来协作,分别是核心线程数和最大线程数,核心线程数是用来表示线程池中的核心线程的数量,也可以称之为可闲置的线程数量,最大线程数是用来表示线程池中最多能够创建的线程数量,创建线程就交给线程工厂ThreadFactory来完成,当线程池接收到一个任务时,如果工作线程数没有达到corePoolSize,那么就会新建一个线程,并绑定任务,直到工作线程的数量达到corePoolSize前都不会重用之前的线程,当工作线程数达到corePoolSize了,又接到新任务的时候,会将任务存放在一个阻塞队列中,等待线程去执行,核心线程中很可能已经有线程执行完自己的任务了,或者有其他线程马上就能处理完当前的任务,并且接下来就能投入到新的任务中去,所以阻塞队列是一种缓冲的机制,给核心线程一个机会让他们充分发挥自己的能力。另外一个值得考虑的原因是,创建线程毕竟是比较昂贵的,不可能一有任务要执行就去创建一个新的进程,所以我们需要为线程池配备一个阻塞队列,用来临时缓存任务,这些任务将等待工作线程执行。
当工作线程数达到 corePoolSize 时,线程池会将新接收到的任务存放在阻塞队列中,而阻塞队列又两种情况:一种是有界的队列,一种是无界的队列。如果是无界队列,那么当核心线程都在忙的时候,所有新提交的任务都会被存放在该无界队列中,这是最大线程数将变得没有意义,因为阻塞队列不会存在被装满的情况。
如果是有界队列,那么当阻塞队列中装满了等待执行的任务,这时再有新任务提交时,线程池就需要创建新的“临时”线程来处理,相当于增派人手来处理任务。
但是创建的“临时”线程是有存活时间的,不可能让他们一直都存活着,当阻塞队列中的任务被执行完毕,并且又没有那么多新任务被提交时,“临时”线程就需要被回收销毁,在被回收销毁之前等待的这段时间,就是非核心线程的存活时间,也就是 keepAliveTime 属性。
其实核心线程跟创建的先后没有关系,而是跟工作线程的个数有关,如果当前工作线程的个数大于核心线程数,那么所有的线程都可能是“非核心线程”,都有被回收的可能。
一个线程执行完了一个任务后,会去阻塞队列里面取新的任务,在取到任务之前它就是一个闲置的线程。
取任务的方法有两种,一种是通过take()方法一直阻塞直到取出任务,另一种是通过poll(keepAliveTime,timeUnit)方法在一定时间内取出任务或者超时,如果超时这个线程就会被回收,请注意核心线程一般不会被回收。每个线程想要保住自己“核心线程”的身份,必须充分努力,尽可能快的获取到任务去执行,这样才能逃避被回收的命运。
以上是关于线程池工作原理的主要内容,如果未能解决你的问题,请参考以下文章