「java线程池拒绝任务」线程池队列拒绝策略不丢失任务
今天给各位分享java线程池拒绝任务的知识,其中也会对线程池队列拒绝策略不丢失任务进行解释,如果能碰巧解决你现在面临的问题,别忘了关注本站,现在开始吧!
本文目录一览:
线程池的拒绝策略示例
Java的线程池中,如果不断往线程池提交任务,最终会发生什么?
如果work queue是一个有界队列,队列放满,线程数量达到maxsize,且没有空闲线程时,再往线程池提交任务会触发线程池的拒绝策略。
线程池有哪些拒绝策略呢?
一共提交8个任务,其中有一个默默被丢弃。
线程池1个核心线程,max线程数为2,work queue大小为5.
可以看到,提交8个任务后,第2个任务被丢弃了。因为第2个任务是oldest,第一个被放进queue的任务。
用这种拒绝策略时要注意,主线程既需要负责创建线程,又需要执行任务,会造成性能问题。
在输出中,能看出,主线程号为1,而提交的任务中,其中一个任务(最后一个被提交的任务)就是由主线程来执行的。
了解了前四种拒绝策略,发现:
abort,discard,discardOldest都会丢弃任务;
callerRun虽然执行了任务,但是会影响主线程性能。
若将work queue设置为无界队列,或者将maxsize设置为最大整数,都有可能造成out of memory。
那么可以通过自定义拒绝策略,让后进来的task阻塞住,有资源了再处理。这样可以让每一个任务都得到执行。
线程池 java中怎么设置拒绝策略
应该是没有这个功能的,因为线程池里面的线程实际上是复用的,即执行完一个Job以后会从Quenue(任务队列)里面取新的JOB。
如果有这样的需求可以:
1)控制JOB的执行时间不能太长,否则可能会造成阻塞;
2)在JOB的实现(run方法)里面做相应的控制;
3)如果JOB有长时间和短时间两种模式,可以考虑放在两个线程池中,避免长时间的任务阻塞短时间的任务;
4)也可以控制等待队列的任务个数,但是Executors默认的Factory方法是没有这个参数的,需要直接new ThreadPoolExecutor
超详细的线程池使用解析
Java 中线程池是运用场景最多的并发框架,几乎所有需要异步或并发执行任务的程序都可以使用线程池。合理的使用线程池可以带来多个好处:
(1) 降低资源消耗 。通过重复利用已创建的线程降低线程在创建和销毁时造成的消耗。
(2) 提高响应速度 。当处理执行任务时,任务可以不需要等待线程的创建就能立刻执行。
(3) 提高线程的可管理性 。线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一分配、调优和监控。
线程池的处理流程如上图所示
线程池中通过 ctl 字段来表示线程池中的当前状态,主池控制状态 ctl 是 AtomicInteger 类型,包装了两个概念字段:workerCount 和 runState,workerCount 表示有效线程数,runState 表示是否正在运行、正在关闭等状态。使用 ctl 字段表示两个概念,ctl 的前 3 位表示线程池状态,线程池中限制 workerCount 为(2^29 )-1(约 5 亿)个线程,而不是 (2^31)-1(20 亿)个线程。workerCount 是允许启动和不允许停止的工作程序的数量。该值可能与实际的活动线程数暂时不同,例如,当 ThreadFactory 在被询问时未能创建线程时,以及退出线程在终止前仍在执行记时。用户可见的池大小报告为工作集的当前大小。 runState 提供主要的生命周期控制,取值如下表所示:
runState 随着时间的推移而改变,在 awaitTermination() 方法中等待的线程将在状态达到 TERMINATED 时返回。状态的转换为:
RUNNING - SHUTDOWN 在调用 shutdown() 时,可能隐含在 finalize() 中
(RUNNING 或 SHUTDOWN)- STOP 在调用 shutdownNow() 时
SHUTDOWN - TIDYING 当队列和线程池都为空时
STOP - TIDYING 当线程池为空时
TIDYING - TERMINATED 当 terminate() 方法完成时
开发人员如果需要在线程池变为 TIDYING 状态时进行相应的处理,可以通过重载 terminated() 函数来实现。
结合上图说明线程池 ThreadPoolExecutor 执行流程,使用 execute() 方法提交任务到线程池中执行时分为4种场景:
(1)线程池中运行的线程数量小于 corePoolSize,创建新线程来执行任务。
(2)线程池中运行线程数量不小于 corePoolSize,将任务加入到阻塞队列 BlockingQueue。
(3)如果无法将任务加入到阻塞队列(队列已满),创建新的线程来处理任务(这里需要获取全局锁)。
(4)当创建新的线程数量使线程池中当前运行线程数量超过 maximumPoolSize,线程池中拒绝任务,调用 RejectedExecutionHandler.rejectedExecution() 方法处理。
源码分析:
线程池创建线程时,会将线程封装成工作线程 Worker,Worker 在执行完任务后,还会循环获取工作队列里的任务来执行。
创建线程池之前,首先要知道创建线程池中的核心参数:
corePoolSize (核心线程数大小):当提交任务到线程池时,线程池会创建一个线程来执行任务,即使其他空闲的基本线程能够执行新任务也会创建线程,直到需要执行的任务数大于核心线程数时就不再创建。
runnableTaskQueue (任务队列):用于保存等待执行任务的阻塞队列。一般选择以下几种:
ArrayBlockingQueue:基于数组的有界阻塞队列,按照 FIFO 原则对元素进行排序。
LinkedBlockingQueue:基于链表的阻塞队列,按照 FIFO 原则对元素进行排序。
SynchronousQueue:同步阻塞队列,也是不存储元素的阻塞队列。每一个插入操作必须要等到另一个 线程调用移除操作,否则插入操作一直处于阻塞状态。
PriorityBlockingQueue:优先阻塞队列,一个具有优先级的无限阻塞队列。
maximumPoolSize (最大线程数大小):线程池允许创建的最大线程数,当队列已满,并且线程池中的线程数小于最大线程数,则线程池会创建新的线程执行任务。当使用无界队列时,此参数无用。
RejectedExecutionHandler (拒绝策略):当任务队列和线程池都满了,说明线程池处于饱和状态,那么必须使用拒绝策略来处理新提交的任务。JDK 内置拒绝策略有以下 4 种:
AbortPolicy:直接抛出异常
CallerRunsPolicy:使用调用者所在的线程来执行任务
DiscardOldestPolicy:丢弃队列中最近的一个任务来执行当前任务
DiscardPolicy:直接丢弃不处理
可以根据应用场景来实现 RejectedExecutionHandler 接口自定义处理策略。
keepAliveTime (线程存活时间):线程池的工作线程空闲后,保持存活的时间。
TimeUnit (存活时间单位):可选单位DAYS(天)、HOURS(小时)、MINUTES(分钟)、MILLISECONDS(毫秒)、MICROSECONDS(微妙)、NANOSECONDS(纳秒)。
ThreadFactory (线程工厂):可以通过线程工厂给创建出来的线程设置有意义的名字。
创建线程池主要分为两大类,第一种是通过 Executors 工厂类创建线程池,第二种是自定义创建线程池。根据《阿里java开发手册》中的规范,线程池不允许使用 Executors 去创建,原因是规避资源耗尽的风险。
创建一个单线程化的线程池
创建固定线程数的线程池
以上两种创建线程池方式使用链表阻塞队列来存放任务,实际场景中可能会堆积大量请求导致 OOM
创建可缓存线程池
允许创建的线程数量最大为 Integer.MAX_VALUE,当创建大量线程时会导致 CPU 处于重负载状态和 OOM 的发生
向线程池提交任务可以使用两个方法,分别为 execute() 和 submit()。
execute() 方法用于提交不需要返回值的任务,所以无法判断任务是否被线程池执行成功。execute() 方法中传入的是 Runnable 类的实例。
submit() 方法用于提交需要返回值的任务。线程池会返回一个 Future 类型的对象,通过 future 对象可以判断任务是否执行成功,并且可以通过 future 的 get() 方法来获取返回值。get() 方法会阻塞当前线程直到任务完成,使用 get(long timeout, TimeUnit unit)方法会阻塞当前线程一段时间后立即返回,这时候可能任务没有执行完。
可以通过调用线程池的 shutdown() 或shutdownNow() 方法来关闭线程池。他们的原理是遍历线程池中的工作线程,然后逐个调用 interrupt() 方法来中断线程,所以无法响应中断任务可能永远无法终止。
shutdown() 和 shutdownNow() 方法的区别在于 shutdownNow 方法首先将线程池的状态设置为 STOP,然后尝试停止正在执行或暂停任务的线程,并返回等待执行任务的列表,而 shutdown 只是将线程池的状态设置成 SHUTDOWN 状态,然后中断所有没有正在执行任务的线程。
线程池使用面临的核心的问题在于: 线程池的参数并不好配置 。一方面线程池的运行机制不是很好理解,配置合理需要强依赖开发人员的个人经验和知识;另一方面,线程池执行的情况和任务类型相关性较大,IO 密集型和 CPU 密集型的任务运行起来的情况差异非常大,这导致业界并没有一些成熟的经验策略帮助开发人员参考。
(1)以任务型为参考的简单评估:
假设线程池大小的设置(N 为 CPU 的个数)
如果纯计算的任务,多线程并不能带来性能提升,因为 CPU 处理能力是稀缺的资源,相反导致较多的线程切换的花销,此时建议线程数为 CPU 数量或+1;----为什么+1?因为可以防止 N 个线程中有一个线程意外中断或者退出,CPU 不会空闲等待。
如果是 IO 密集型应用, 则线程池大小设置为 2N+1. 线程数 = CPU 核数 目标 CPU 利用率 (1 + 平均等待时间 / 平均工作时间)
(2)以任务数为参考的理想状态评估:
1)默认值
2)如何设置 * 需要根据相关值来决定 - tasks :每秒的任务数,假设为500~1000 - taskCost:每个任务花费时间,假设为0.1s - responsetime:系统允许容忍的最大响应时间,假设为1s
以上都为理想值,实际情况下要根据机器性能来决定。如果在未达到最大线程数的情况机器 cpu load 已经满了,则需要通过升级硬件和优化代码,降低 taskCost 来处理。
(仅为简单的理想状态的评估,可作为线程池参数设置的一个参考)
与主业务无直接数据依赖的从业务可以使用异步线程池来处理,在项目初始化时创建线程池并交给将从业务中的任务提交给异步线程池执行能够缩短响应时间。
严禁在业务代码中起线程!!!
当任务需要按照指定顺序(FIFO, LIFO, 优先级)执行时,推荐创建使用单线程化的线程池。
本文章主要说明了线程池的执行原理和创建方式以及推荐线程池参数设置和一般使用场景。在开发中,开发人员需要根据业务来合理的创建和使用线程池达到降低资源消耗,提高响应速度的目的。
原文链接:
自定义线程池拒绝策略及有界无界队列
核心线程数:实际运行的线程数
最大线程数:最大可以创建的线程数
(1)ThreadPoolExecutor.AbortPolicy 丢弃任务,并抛出 RejectedExecutionException 异常。
(2)ThreadPoolExecutor.CallerRunsPolicy:该任务被线程池拒绝,由调用 execute方法的线程执行该任务。
(3)ThreadPoolExecutor.DiscardOldestPolicy : 抛弃队列最前面的任务,然后重新尝试执行任务。
(4)ThreadPoolExecutor.DiscardPolicy,丢弃任务,不过也不抛出异常。
当线程池的任务缓存队列已满并且线程池中的线程数目达到maximumPoolSize,如果还有任务到来就会采取任务拒绝策略。
一个任务通过 execute(Runnable) 方法被添加到线程池,任务就是一个 Runnable 类型的对象,任务的执行方法就是 Runnable 类型对象的 run() 方法。
当一个任务通过 execute(Runnable) 方法欲添加到线程池时,线程池采用的策略如下(即添加任务的策略):
如果此时线程池中的数量小于 corePoolSize ,即使线程池中的线程都处于空闲状态,也要创建新的线程来处理被添加的任务。
如果此时线程池中的数量等于 corePoolSize ,但是缓冲队列 workQueue 未满,那么任务被放入缓冲队列。
如果此时线程池中的数量大于 corePoolSize ,缓冲队列 workQueue 满,并且线程池中的数量小于maximumPoolSize ,建新的线程来处理被添加的任务。
如果此时线程池中的数量大于 corePoolSize ,缓冲队列 workQueue 满,并且线程池中的数量等于maximumPoolSize ,那么通过 handler 所指定的策略来处理此任务。
任务处理的优先级(顺序)为:
核心线程 corePoolSize 、任务队列 workQueue 、最大线程 maximumPoolSize ,如果三者都满了,使用 handler处理被拒绝的任务。当线程池中的线程数量大于 corePoolSize 时,如果某线程空闲时间超过 keepAliveTime ,线程将被终止。这样,线程池可以动态的调整池中的线程数。
从有界无界上分
常见的有界队列为
ArrayBlockingQueue 基于数组实现的阻塞队列
LinkedBlockingQueue 其实也是有界队列,但是不设置大小时就是无界的。
ArrayBlockingQueue 与 LinkedBlockingQueue 对比一哈
ArrayBlockingQueue 实现简单,表现稳定,添加和删除使用同一个锁,通常性能不如后者
LinkedBlockingQueue 添加和删除两把锁是分开的,所以竞争会小一些
SynchronousQueue 比较奇葩,内部容量为零,适用于元素数量少的场景,尤其特别适合做交换数据用,内部使用 队列来实现公平性的调度,使用栈来实现非公平的调度,在Java6时替换了原来的锁逻辑,使用CAS代替了
上面三个队列他们也是存在共性的
put take 操作都是阻塞的
offer poll 操作不是阻塞的,offer 队列满了会返回false不会阻塞,poll 队列为空时会返回null不会阻塞
补充一点,并不是在所有场景下,非阻塞都是好的,阻塞代表着不占用CPU,在有些场景也是需要阻塞的,put take 存在必有其存在的必然性
常见的无界队列
ConcurrentLinkedQueue 无锁队列,底层使用CAS操作,通常具有较高吞吐量,但是具有读性能的不确定性,弱一致性——不存在如ArrayList等集合类的并发修改异常,通俗的说就是遍历时修改不会抛异常
PriorityBlockingQueue 具有优先级的阻塞队列
DelayedQueue 延时队列,使用场景
缓存:清掉缓存中超时的缓存数据
任务超时处理
补充:内部实现其实是采用带时间的优先队列,可重入锁,优化阻塞通知的线程元素leader
LinkedTransferQueue 简单的说也是进行线程间数据交换的利器,在SynchronousQueue 中就有所体现,并且并发大神 Doug Lea 对其进行了极致的优化,使用15个对象填充,加上本身4字节,总共64字节就可以避免缓存行中的伪共享问题,其实现细节较为复杂,可以说一下大致过程:
比如消费者线程从一个队列中取元素,发现队列为空,他就生成一个空元素放入队列 , 所谓空元素就是数据项字段为空。然后消费者线程在这个字段上旅转等待。这叫保留。直到一个生产者线程意欲向队例中放入一个元素,这里他发现最前面的元素的数据项字段为 NULL,他就直接把自已数据填充到这个元素中,即完成了元素的传送。大体是这个意思,这种方式优美了完成了线程之间的高效协作。参考自
java线程池拒绝任务的介绍就聊到这里吧,感谢你花时间阅读本站内容,更多关于线程池队列拒绝策略不丢失任务、java线程池拒绝任务的信息别忘了在本站进行查找喔。
发布于:2022-12-05,除非注明,否则均为
原创文章,转载请注明出处。