多线程 线程池ThreadPoolExecutor
多线程 线程池ThreadPoolExecutor
线程池是java提供的线程管理容器,它允许开发者定义多个线程,并开启一些额外线程,提供一个自定义的任务队列和队列满时对新任务的处理策略。
使用
ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor(
10,// 核心线程数,这些线程不会被淘汰
15,// 最大线程数 = 核心线程数+额外线程数
10,// 额外线程数超时淘汰时间
TimeUnit.SECONDS,//额外线程数超时淘汰时间单位
new ArrayBlockingQueue<>(10),//任务队列
new ThreadPoolExecutor.AbortPolicy()//拒绝策略
);
// 提交任务,lambda表达式
poolExecutor.submit(()->{/*业务代码*/});
...
// 提交任务,多线程实体
Runnable runnable = new RunnableDemo();
poolExecutor.submit(runnable);
...
// 拒绝接受任务,执行完所有池内以及任务队列中的全部任务
service.shutdown();
// 在超时时间内等待任务执行完成,常用于if条件;在线程池中工作完全结束后执行其他业务
bool is_finish=poolExecutor.awaitTermination(100, TimeUnit.HOURS)
// 拒绝接受任务且立即干掉所有正在执行的任务,不推荐使用
service.shutdownNow();
线程池工作流程
以上述代码为例,初始化的线程池的任务队列和是空的,线程池中仅存在有核心线程,线程也无任务执行,第一次提交任务给线程池,因为核心线程无一个有任务正在执行,所以直接随机调用一个核心线程执行提交的任务。
接着任务变多,核心线程数已经不够用了,若此时已提交 10个任务,它们都在被核心线程执行且均未执行完毕,那么第11个任务提交后,线程池开启额外线程数执行,注意哈!此时第11个任务并未被放入任务队列中;随着任务越来越多,某一时刻已经提交到第16个任务,且前15个任务均未被执行完毕;此时第16个任务就被安排进入任务队列,等待第一个执行完任务的线程池取用它;
某一时刻,在前15个任务没有执行完毕的前提下,你又提交了9个任务,算上上次提交的第16个任务,已经提交了10个不能被立即执行的任务了,它们均被放入任务队列中,已知任务队列最大可容纳10个任务,当第26个任务被提交时,此时队列已经容纳不了这个任务了,所以这个任务应该被怎么处理呢?这就取决于你给出的拒绝策略了。
任务队列
⭐ ArrayBlockingQueue(基础)
- 基于数组的有界阻塞队列。它按照先进先出(FIFO)的原则存储和提供元素。
- 队列长度固定,可通过构造函数指定。
- 当队列满时,
put
操作将阻塞,直到队列中的元素被消费。
⭐ LinkedBlockingQueue(基础)
- 基于链表结构的阻塞队列,可以设置容量上限,如果不设置则默认为
Integer.MAX_VALUE
,即几乎无限。 - 它同样遵循FIFO原则。
- 适合于需要一个大容量队列的场景,或者需要一个无限队列来缓冲任务。
⭐ PriorityBlockingQueue(高阶)
- 一个无界的优先级队列,使用堆数据结构实现。
- 元素按照自然排序或比较器决定的顺序进行排序。
- 当队列满时,
put
操作不会阻塞,因为它是无界的,但是它会抛出IllegalArgumentException
如果队列无法接受更多的元素(例如,因为元素不可比较)。
⭐ SynchronousQueue(高阶)
- 一个不存储元素的阻塞队列,每个
put
操作必须等待一个take
操作,反之亦然。 - 它常被用作线程间传递数据的通道,而不是一个真正的队列。
- 特别适用于希望直接将任务传递给线程,而无需缓冲的情况。
⭐DelayQueue
- 一个持有
Delayed
元素的无界阻塞队列。 - 元素只有在其延迟到期后才能被消费者线程提取。
- 通常用于实现定时任务或延迟任务的场景。
拒绝策略
⭐ AbortPolicy
- 默认的拒绝策略
- 当线程池无法接受更多任务时,
AbortPolicy
会抛出RejectedExecutionException
异常,这会中断提交任务的线程。这种策略对于调试很有帮助,因为它可以立即通知你线程池已经超载,生产环境中勿用。
⭐ CallerRunsPolicy
- 当线程池无法接受更多任务时,不将其放入队列或创建新的线程,而是提交任务的线程直接执行此任务。相当于回到一个没有线程池的情况,即调用
execute
方法的线程自己执行自己提交的任务。这种策略可能会降低系统的响应速度,因为它会占用原本用于提交任务的线程。
⭐DiscardPolicy
- 默默地丢弃无法处理的任务,不抛出任何异常
- 这种策略对于统计计算的任务丢失会导致灾难性后果。
⭐DiscardOldestPolicy
- 当线程池无法接受更多任务时,丢弃任务队列中最早的任务,然后尝试再次提交当前任务。这种策略试图保护最近的任务,牺牲最早的任务,它可能在处理实时或最新数据时有用,但如果任务依赖于队列中的顺序,这种策略可能会导致问题。
⚠️ 注意事项
核心线程数不能大于最大线程数
最大线程数不能小于核心线程数,且建议最大线程数+队列长度≥提交的任务数
仅接受实现了
Runnable
接口的任务额外线程数超时淘汰时间&额外线程数超时淘汰时间单位:根据实际运行情况设置,最好运行几次多线程任务后计算出一个平衡值
任务队列长度最好和最大线程数相等,不然很容易出以下的BUG
Exception in thread "main" java.util.concurrent.RejectedExecutionException: Task java.util.concurrent.FutureTask@7c30a502 rejected from java.util.concurrent.ThreadPoolExecutor@5594a1b5[Running, pool size = 100, active threads = 100, queued tasks = 10, completed tasks = 0] at java.util.concurrent.ThreadPoolExecutor$AbortPolicy.rejectedExecution(ThreadPoolExecutor.java:2063) at java.util.concurrent.ThreadPoolExecutor.reject(ThreadPoolExecutor.java:830) at java.util.concurrent.ThreadPoolExecutor.execute(ThreadPoolExecutor.java:1379) at java.util.concurrent.AbstractExecutorService.submit(AbstractExecutorService.java:112)
建议使用for循环提交任务时给一个sleep睡眠时间,让主线程让出时间片,让线程池抢占使用,不然一下子提交的数量超过最大线程数+任务队列,会报错!
for (int i = 0; i < 200; i++) { Thread.sleep(10); poolExecutor.submit(/*业务*/); }
本文来自博客园,作者:勤匠,转载请注明原文链接:https://www.cnblogs.com/JarryShu/articles/18319644
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· AI与.NET技术实操系列:基于图像分类模型对图像进行分类
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现