【Android】线程池的解析
引言
在Android当中根据用途分为主线程与子线程,主线程当中主要处理与界面相关的操作,子线程主要进行耗时操作。除了Thread本身以外,在Android当中还有很多扮演者线程的角色,比如AsyncTask( 底层为线程池,但是现在并不推荐使用)、IntentService和一个特殊的线程HandlerThread。
对于不同的线程有不同的使用场景,AsyncTask封装了线程池和Handler,主要是为了在子线程里面更新UI。HandlerThread是一种具有消息循环的线程,它的内部可以使用Handler。IntentService是一个服务,系统对内部进行了封装使其更方便的进行后台服务,内部采用HandlerThread来执行任务,当任务执行完毕IntentService会自动退出,它的作用很像一个后台进程(被弃用,WorkManager 或 JobIntentService。WorkManager 是 Google 推荐的用于执行后台任务的解决方案,它支持一次性任务和周期性任务,并能够处理任务的重试、链式依赖等。而 JobIntentService 可以在后台处理任务,并且在需要时重新启动服务,适用于需要向后兼容较旧的 Android 版本的场景)。
在操作系统当中,线程是操作系统调度的最小单元, 同时线程又是一种受限的系统资源,即线程不可以无限制的产生,并且线程的创建和销毁都会有相应的开销。当系统当中存在大量的线程的时候,系统会通过时间片轮转的方式调度线程,因此线程不可能做到绝对的并行,除非线程数量小于CPU的核心数,但一般来说这是不可能的。但是在程序当中频繁创建和销毁线程显然不是高效的做法,应该采用线程池,接下来就看看Android中的线程池吧!
使用线程池的优点
- 重用线程池中的线程,避免因为线程的创建和销毁所带来的性能开销。
- 能有效控制线程池的最大并发数,避免大量的线程之间因互相抢占系统资源而导致的阻塞现象。
- 能够对线程进行简单的管理,并提供定时执行以及指定间隔循环执行等功能。
ThreadPoolExecutor
ThreadPoolExecutor 是 Java 中 Executor 框架的一部分,它实现了 Executor 接口和 ExecutorService 接口。这个类允许你创建一个线程池,并且可以控制任务的并发执行,它是线程池的核心实现类。
一共有4个构造方法,接下来我们就看看拥有最多参数的构造方法

- corePoolSize(核心线程数)
默认情况下线程池是空的,只有提交任务时才会创建线程。如果当前运行的线程数少于corePoolSize,则会创建新的线程来处理任务;如何当前运行的线程数等于或者多于corePoolSize,则不会创建新线程。核心线程通常不会被回收(除非设置了允许回收的核心线程数)。如果调用线程池的prestartAllcoreThread方法,则线程池会提前创建并开启所有的核心线程来处理任务。
- maximumPoolSize(最大线程数)
这是线程池中允许的最大线程数量,包括核心线程和非核心线程。当队列满了并且正在执行的线程数少于最大线程数时,线程池会尝试创建新的线程来处理任务。如果队列满了且线程数已达到最大线程数,新提交的任务将被拒绝。
- keepAliveTime(非核心线程空闲存活时间)
这是非核心线程在终止前等待新任务的最长时间。当线程池中的线程数超过核心线程数时,这些额外的线程(非核心线程)在空闲时会等待新任务的到来。如果超过这个时间还没有新任务,线程将被回收。对于核心线程,这个参数无效,除非设置了允许回收的核心线程数(allowCoreThreadTimeOut(true)方法来设置)。
- unit(时间单位)
这是keepAliveTime参数的时间单位,可以是毫秒、秒、分钟等。
- workQueue(工作队列)
这是一个阻塞队列,用于存放待执行的任务。当所有核心线程都在忙碌时,新提交的任务会被放入这个队列中。如果队列满了,线程池会尝试创建新的线程来处理任务,直到达到最大线程数。
- threadFactory(线程工厂)
这是一个ThreadFactory对象,用于创建新线程。线程工厂允许你自定义线程的创建过程,例如设置线程的名称、优先级、守护状态等。默认的线程工厂通常就足够了,但自定义线程工厂可以提供更多的控制和调试信息。
- handler(拒绝/饱和策略)
这是一个RejectedExecutionHandler对象,用于处理当任务太多,无法被线程池及时处理时的情况,即任务队列和线程池都满了的情况。常见的拒绝策略有:
- AbortPolicy:默认策略,表示无法处理新任务,抛出
RejectedExecutionException。 - CallerRunsPolicy:在调用者的线程中执行任务。
- DiscardPolicy:默默丢弃无法处理的任务。
- DiscardOldestPolicy:丢弃队列中最旧的任务,重新提交当前的新任务。
线程池的处理流程与原理

根据流程图我们可以看到,当我们执行ThreadPoolExecutor的execute方法,会有各种的情况:
- 如果线程池中的线程数未达到核心线程数,则创建核心线程处理任务
- 如果线程数大于或等于核心线程数,则将任务加入任务队列,线程池中的空闲线程会不断地从任务队列中取出任务进行处理
- 如果任务队列满了,并且线程数没有达到最大线程数,则创建非核心线程去处理任务
- 如果线程数超过了最大线程数,则执行饱和策略
线程池的种类
我们可以直接或者间接的通过配置来实现自己的线程池的功能特性。
FixedThreadPool
先来看看它的构造函数:

public static ExecutorService newFixedThreadPool(int nThreads) {return new ThreadPoolExecutor(nThreads, nThreads,0L, TimeUnit.MILLISECONDS,new LinkedBlockingQueue<Runnable>());
}
nThreads:核心线程数和最大线程数都被设置为nThreads,这意味着线程池的大小是固定的,不会动态变化。0L:非核心线程的空闲存活时间被设置为0。由于所有线程都是核心线程,这个值实际上并不会影响线程池的行为。TimeUnit.MILLISECONDS:空闲存活时间的时间单位是毫秒。new LinkedBlockingQueue<Runnable>():工作队列是一个无界的LinkedBlockingQueue。由于线程池的大小是固定的,这个无界队列意味着如果所有线程都在忙碌,新提交的任务将会被放入队列中,直到队列满为止。
是可重用固定线程数的线程池,在一开始创建就已经规定了线程数,意味着只有核心线程没有非核心线程,即创建的都是核心线程,并且这些线程会一直存活直到线程池被关闭,即使它们处于空闲状态也不会被回收。它的提交任务执行示意图:

CachedThreadPool

public static ExecutorService newCachedThreadPool() {return new ThreadPoolExecutor(0, Integer.MAX_VALUE,60L, TimeUnit.SECONDS,new SynchronousQueue<Runnable>());
}
0:核心线程数被设置为0,这意味着线程池在初始时没有任何线程,线程池中的线程都是非核心线程。Integer.MAX_VALUE:最大线程数被设置为Integer.MAX_VALUE(约21亿),这意味着线程池理论上可以创建非常多的线程。但由于实际物理和操作系统资源的限制,这个数字通常不会达到。60L:非核心线程的空闲存活时间被设置为60秒。当线程池中的线程空闲超过这个时间,它们将被回收。
CachedThreadPool线程池,它会根据需要创建新线程,但如果线程空闲超过一定时间(默认60秒),则会被回收。这种线程池适合执行很多短期异步任务的程序。

SingleThreadExecutor
SingleThreadExecutor是使用单个线程的线程池,当当前没有运行的线程的时候,就会创建一个新线程来处理任务,如果有运行的线程就将其添加到阻塞队列当中。

public static ExecutorService newSingleThreadExecutor() {return new FinalizableDelegatedExecutorService(new ThreadPoolExecutor(1, 1,0L, TimeUnit.MILLISECONDS,new LinkedBlockingQueue<Runnable>()));
}
1:核心线程数和最大线程数都被设置为1,这意味着线程池始终只有一个线程。0L:非核心线程的空闲存活时间被设置为0。由于只有一个线程,这个值实际上并不会影响线程池的行为。

ScheduledThreadPool
ScheduledThreadPool是一个能实现和定时和周期性任务的线程池。

public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {return new ScheduledThreadPoolExecutor(corePoolSize);
}public ScheduledThreadPoolExecutor(int corePoolSize) {super(corePoolSize, Integer.MAX_VALUE,DEFAULT_KEEPALIVE_MILLIS, MILLISECONDS,new DelayedWorkQueue());
}
new DelayedWorkQueue():工作队列,这里使用了一个延迟工作队列。这个队列可以存储待执行的任务,并按照任务的延迟时间进行排序,确保最早需要执行的任务可以被优先处理

当执行ScheduledThreadPoolExecutor的scheduleAtFixedRate 或者scheduleWithFixedDelay 方法时,会向 DelayedWorkQueue 添加一个实现 RunnableScheduledFuture 接口的 ScheduledFutureTask(任务的包装类),并会检查运行的线程数是否达到了corePoolSize(核心线程数)。如果没有达到,则新建线程并启动它,但并不是立即去执行任务,而是去DelayedWorkQueue中取ScheduledFutureTask,然后执行任务。如果运行的线程数达到了corePoolSize时,则将任务添加到 DelayedWorkQueue 中。DelayedWorkQueue 会将任务进行排序,先要执行的任务放在队列的前面。其跟此前介绍的线程池不同的是,当执行完任务后,会将ScheduledFutureTask中的 time变量改为下次要执行的时间并放回 DelayedWorkQueue中。
总结
| 线程池类型 | 特点 | 适用场景 | 核心线程数 | 最大线程数 | 空闲线程存活时间 |
|---|---|---|---|---|---|
| FixedThreadPool | 拥有固定数量的线程,线程数不变。 | 负载较重的服务器,需要限制线程数量的场景。 | 固定 | 固定 | 无 |
| CachedThreadPool | 根据需要创建新线程,空闲线程会被回收。 | 执行很多短期异步任务的程序。 | 0 | Integer.MAX_VALUE | 60秒 |
| ScheduledThreadPool | 可以安排在给定延迟后运行命令或定期地执行。 | 需要任务在后台定期执行或重复执行的程序。 | 固定 | 固定 | 60秒 |
| SingleThreadExecutor | 只有一个线程,所有任务按照提交顺序依次执行。 | 需要保证任务顺序执行的场景。 | 1 | 1 | 无 |
文章到这里就结束了!
相关文章:
【Android】线程池的解析
引言 在Android当中根据用途分为主线程与子线程,主线程当中主要处理与界面相关的操作,子线程主要进行耗时操作。除了Thread本身以外,在Android当中还有很多扮演者线程的角色,比如AsyncTask( 底层为线程池,…...
集群聊天服务器(8)用户登录业务
目录 登录状态业务层代码数据模型层代码记录用户的连接信息以及线程安全问题客户端异常退出业务 登录状态 登录且状态变为online 业务层代码 #include "chatservice.hpp" #include "public.hpp" #include <string> #include <muduo/base/Loggi…...
Go语言中的错误嵌套
在Go语言中,错误处理是程序健壮性的关键。Go 1.13版本引入了错误值的嵌套和链式处理,使得错误信息的传递和处理更加灵活和强大。这种机制允许我们在错误中嵌套另一个错误,从而创建一个错误链,这有助于调试和错误跟踪。 错误嵌套的…...
51单片机基础 06 串口通信与串口中断
目录 一、串口通信 二、串口协议 三、原理图 四、串口通信配置参数 1、常用的串行口工作方式1 2、数据发送 3、数据接收 4、波特率计算 5、轮询接收 6、中断接收 一、串口通信 串口通信是一种常见的数据传输方式,广泛用于计算机与外部设备或嵌入式系统之间…...
Elasticsearch:更好的二进制量化(BBQ)对比乘积量化(PQ)
作者:来自 Elastic Benjamin Trent 为什么我们选择花时间研究更好的二进制量化而不是在 Lucene 和 Elasticsearch 中进行生产量化。 我们一直在逐步使 Elasticsearch 和 Lucene 的向量搜索变得更快、更实惠。我们的主要重点不仅是通过 SIMD 提高搜索速度࿰…...
【GNU】gcc -g编译选项 -g0 -g1 -g2 -g3 -gdwarf
1、gcc -g的作用 GCC 的 -g 选项用于在编译时生成调试信息,这些信息会嵌入到生成的目标文件或可执行文件中,主要目的是为了支持调试器(如 gdb)对程序的调试工作。 1.1 生成调试信息 当你在编译代码时使用 -g 选项,GCC…...
MySQL【六】
存储过程 存储过程是一组为了完成特定功能的 SQL 语句集,经编译创建并保存在数据库中,用户可通过指定存储过程的名字并给定参数(需要时)来调用执行。 简单的说存储过程就是具有名字的一段代码。 存储过程的创建 CREATE PROC[ED…...
杰发科技AC7801——ADC定时器触发的简单使用
使用场景 在需要多次采样结果的情况下,比如1s需要10w次的采样结果,可以考虑使用定时器触发采样,定时器设置多少的时间就会多久采样转换一次。 再加上使用dma,采样的结果直接放在dma的数组里面。 实现了自动采样,自动…...
VTK知识学习(8)-坐标系统
1、概述 计算机图形学里常用的坐标系统有4种: 1)、Model坐标系统。定义模型时所采用的坐标系统,通常是局部的笛卡儿坐标系。 2)、World坐标系统。是放置Actor的三维空间坐标系。 Actor(vtkActor类&am…...
IO流部分串讲
一、IO流的概念简析: java将输入与输出比喻为"流",英文:Stream. 就像生活中的"电流","水流"一样,它是以同一个方向顺序移动的过程.只不过这里流动的是字节(2进制数据).所以在IO中有输入流和输出流之分,我们理解他们是连接…...
Excel——宏教程(2)
Excel——宏教程(2) 一)、处理单元格 1、直接赋值与引用 将变量、常量值直接赋给单元格、或将单元格的值直接赋给变量、常量,这是在excel中最简单的单元格赋值及引用方法。 如下例将工作表"Sheet1"A1单元格的值赋给Integer变量I,并将I1的值…...
unity 中 RectTransform 的常用几个属性
RectTransform rectTransform this.GetComponent<RectTransform>(); rectTransform this.transform as RectTransform; Vector3 vector1 rectTransform.position; //自身轴心点相对于锚点的位置(编译器显示的pos) …...
项目-摄像
树莓派摄像头使用方法 Camera教程 https://www.raspi.cc/index.php?cread&id53&page1 nanopc-t4 https://www.raspi.cc/index.php?cread&id53&page1 摄像头型号 Raspberry Pi Camera Rev 1.3 检测故障 dmesg | grep -i mipi piNanoPC-T4:~$ dmesg | …...
摄像机ISP和DSP的区别?
影像处理器是现代数字相机、手机等电子设备中极其重要的一部分,它能够对传感器采集的图像进行多种操作,从而得到更高质量的图像。常见的两种影像处理芯片有ISP(Image Signal Processor)和DSP(Digital Signal Processor…...
Ubuntu24安装配置NDK
1、下载NDK 下载压缩包,下载地址如下,建议下载LTS支持版本。 https://developer.android.google.cn/ndk/downloads?hlcs 2、解压缩 将NDK解压到指定文件夹。如:/opt 或者先解压,再移动到指定目录下。 3、配置环境变量 找到…...
【Next】中间件
概述 Next.js 的 中间件 (Middleware) 是一种在请求完成之前运行的函数,用于对入站请求进行处理和操作。它可以在路由匹配前执行逻辑,用于身份验证、请求重写、重定向、设置响应头等任务。 使用场景 身份验证:在用户访问页面前检查登录状态…...
Vulnhub靶场案例渗透[11]- Momentum2
文章目录 一、靶场搭建1. 靶场描述2. 下载靶机环境3. 靶场搭建 二、渗透靶场1. 确定靶机IP2. 探测靶场开放端口及对应服务3. 扫描网络目录结构4. 代码审计5. 反弹shell6. 提权 一、靶场搭建 1. 靶场描述 - Difficulty : medium - Keywords : curl, bash, code reviewThis wor…...
STM32设计防丢防摔智能行李箱-分享
目录 目录 前言 一、本设计主要实现哪些很“开门”功能? 二、电路设计原理图 1.电路图采用Altium Designer进行设计: 2.实物展示图片 三、程序源代码设计 四、获取资料内容 前言 随着科技的不断发展,嵌入式系统、物联网技术、智能设备…...
Vue Mixin混入机制
在 Vue.js 中,Mixin(混入)是一种可复用代码的机制,用于在多个组件之间共享逻辑。通过混入,可以将通用功能提取到一个独立的文件中,然后在组件中引入并使用,而无需重复代码。 基本概念 Mixin 是…...
数据库类型建表
接着上次的数据库笔记: 初始数据库 (是博主自己写的) 1.数据库类型 1.1数值类型 数据类型大小说明对应JAVA类型BIT[(M)]M指定位数,默认值为1二进制数,M的范围从1—64,存储数值范围从0—2^M-1常用Bool…...
【大模型RAG】Docker 一键部署 Milvus 完整攻略
本文概要 Milvus 2.5 Stand-alone 版可通过 Docker 在几分钟内完成安装;只需暴露 19530(gRPC)与 9091(HTTP/WebUI)两个端口,即可让本地电脑通过 PyMilvus 或浏览器访问远程 Linux 服务器上的 Milvus。下面…...
Java - Mysql数据类型对应
Mysql数据类型java数据类型备注整型INT/INTEGERint / java.lang.Integer–BIGINTlong/java.lang.Long–––浮点型FLOATfloat/java.lang.FloatDOUBLEdouble/java.lang.Double–DECIMAL/NUMERICjava.math.BigDecimal字符串型CHARjava.lang.String固定长度字符串VARCHARjava.lang…...
React19源码系列之 事件插件系统
事件类别 事件类型 定义 文档 Event Event 接口表示在 EventTarget 上出现的事件。 Event - Web API | MDN UIEvent UIEvent 接口表示简单的用户界面事件。 UIEvent - Web API | MDN KeyboardEvent KeyboardEvent 对象描述了用户与键盘的交互。 KeyboardEvent - Web…...
解决本地部署 SmolVLM2 大语言模型运行 flash-attn 报错
出现的问题 安装 flash-attn 会一直卡在 build 那一步或者运行报错 解决办法 是因为你安装的 flash-attn 版本没有对应上,所以报错,到 https://github.com/Dao-AILab/flash-attention/releases 下载对应版本,cu、torch、cp 的版本一定要对…...
OpenPrompt 和直接对提示词的嵌入向量进行训练有什么区别
OpenPrompt 和直接对提示词的嵌入向量进行训练有什么区别 直接训练提示词嵌入向量的核心区别 您提到的代码: prompt_embedding = initial_embedding.clone().requires_grad_(True) optimizer = torch.optim.Adam([prompt_embedding...
Linux --进程控制
本文从以下五个方面来初步认识进程控制: 目录 进程创建 进程终止 进程等待 进程替换 模拟实现一个微型shell 进程创建 在Linux系统中我们可以在一个进程使用系统调用fork()来创建子进程,创建出来的进程就是子进程,原来的进程为父进程。…...
#Uniapp篇:chrome调试unapp适配
chrome调试设备----使用Android模拟机开发调试移动端页面 Chrome://inspect/#devices MuMu模拟器Edge浏览器:Android原生APP嵌入的H5页面元素定位 chrome://inspect/#devices uniapp单位适配 根路径下 postcss.config.js 需要装这些插件 “postcss”: “^8.5.…...
让回归模型不再被异常值“带跑偏“,MSE和Cauchy损失函数在噪声数据环境下的实战对比
在机器学习的回归分析中,损失函数的选择对模型性能具有决定性影响。均方误差(MSE)作为经典的损失函数,在处理干净数据时表现优异,但在面对包含异常值的噪声数据时,其对大误差的二次惩罚机制往往导致模型参数…...
Python Einops库:深度学习中的张量操作革命
Einops(爱因斯坦操作库)就像给张量操作戴上了一副"语义眼镜"——让你用人类能理解的方式告诉计算机如何操作多维数组。这个基于爱因斯坦求和约定的库,用类似自然语言的表达式替代了晦涩的API调用,彻底改变了深度学习工程…...
Python 实现 Web 静态服务器(HTTP 协议)
目录 一、在本地启动 HTTP 服务器1. Windows 下安装 node.js1)下载安装包2)配置环境变量3)安装镜像4)node.js 的常用命令 2. 安装 http-server 服务3. 使用 http-server 开启服务1)使用 http-server2)详解 …...
