Android学习总结之线程池篇
一、线程池参数调优实战真题
真题 1:直播 APP 弹幕加载线程池设计
题目描述:直播 APP 需要实时加载弹幕数据(网络请求,IO 密集型),同时渲染弹幕视图(UI 操作需切主线程),现有线程池导致弹幕卡顿,如何优化?
解题思路:
- 参数调整:
corePoolSize
:对于 IO 密集型任务,设为2 * CPU核心数
,例如 8 核设备则corePoolSize = 16
。maximumPoolSize
:适当增大到3 * CPU核心数
(即 24),以应对高并发场景。workQueue
:使用有界队列ArrayBlockingQueue(100)
,防止内存溢出。keepAliveTime
:设为60秒
,允许非核心线程存活更久。
- 任务拆分:
- 网络请求任务提交至线程池。
- 通过
Handler
将渲染任务切换到主线程。
int cpuCores = Runtime.getRuntime().availableProcessors();
// 创建线程池
ExecutorService executor = new ThreadPoolExecutor(2 * cpuCores, // 核心线程数,针对IO密集型任务设置为2 * CPU核心数3 * cpuCores, // 最大线程数,增大以应对高并发60L, // 非核心线程空闲时的存活时间TimeUnit.SECONDS, // 存活时间单位为秒new ArrayBlockingQueue<>(100), // 有界队列,容量为100new ThreadPoolExecutor.CallerRunsPolicy() // 拒绝策略,任务被调用线程执行
);executor.submit(() -> {// 网络请求获取弹幕数据List<DanmuData> data = networkRequest(); // 通过Handler切换到主线程渲染弹幕new Handler(Looper.getMainLooper()).post(() -> { renderDanmu(data);});
});
回答话术:
“针对弹幕加载的 IO 密集型任务,我会从三个层面优化。首先是 线程池参数调整:
corePoolSize
设置为2 * CPU核心数
(如 8 核设备设为 16),因为 IO 任务等待时线程不占用 CPU,更多核心线程能提升并发效率;maximumPoolSize
扩展到3 * CPU核心数
(即 24),应对弹幕突发高流量;- 使用
ArrayBlockingQueue(100)
限制任务队列长度,防止内存溢出; keepAliveTime
设为 60 秒,允许非核心线程存活,减少频繁创建销毁开销。
其次, 任务拆分:网络请求提交到线程池异步执行,通过Handler
切换到主线程渲染弹幕,避免阻塞 UI。
最后, 拒绝策略 选用CallerRunsPolicy
,将被拒绝的任务回退到调用线程(通常是主线程)。这样在高并发时,若主线程空闲可临时处理任务,同时提示开发者线程池已满载,需进一步优化参数或任务分配。”
面试追问:
- 问:为什么使用
CallerRunsPolicy
拒绝策略? - 答:该策略将被拒绝的任务交给调用者线程(通常是主线程)执行,避免任务丢弃。在弹幕高并发场景下,主线程空闲时可临时处理部分任务,同时提醒开发者线程池已满载,需优化参数或任务分配。
真题 2:图片编辑 APP 线程池优化
题目描述:图片编辑 APP 在批量处理图片时(解码、裁剪、压缩,均为 CPU 密集型),出现手机发热严重且处理速度慢的问题,如何优化?
优化方案:
- 参数调整:
corePoolSize
:设为CPU核心数 + 1
(如 9)。maximumPoolSize
:与核心线程数保持一致(9),避免过多线程上下文切换。keepAliveTime
:设为0
,快速回收空闲线程。
- 性能监控:
- 使用
StrictMode
监控线程池使用情况。 - 分析 CPU 使用率,动态调整任务数量。
- 使用
int cpuCores = Runtime.getRuntime().availableProcessors();
// 创建线程池
ExecutorService executor = new ThreadPoolExecutor(cpuCores + 1, // 核心线程数,针对CPU密集型任务设置为CPU核心数 + 1cpuCores + 1, // 最大线程数,与核心线程数相同0L, // 非核心线程空闲时的存活时间设为0TimeUnit.MILLISECONDS, // 存活时间单位为毫秒new ArrayBlockingQueue<>(50), // 有界队列,容量为50new ThreadPoolExecutor.DiscardOldestPolicy() // 拒绝策略,丢弃队列中最老的任务
);
“对于 CPU 密集型的图片处理任务,优化需兼顾效率和资源消耗。
首先调整 线程池参数:
corePoolSize
设为CPU核心数 + 1
(如 8 核设备设为 9),充分利用 CPU 资源且减少上下文切换;maximumPoolSize
与核心线程数保持一致,避免创建多余线程加剧 CPU 负载;keepAliveTime
设为 0,让非核心线程立即回收,释放资源。
其次, 性能监控 必不可少:通过StrictMode
监控线程池使用,例如检测耗时任务或线程泄漏;结合 CPU 使用率动态调整任务数量,比如当 CPU 负载过高时,暂停部分任务或降低并发量。
最后, 拒绝策略 采用DiscardOldestPolicy
,丢弃队列中等待最久的任务,保证新提交的紧急任务优先执行,避免任务堆积。”
二、任务依赖与同步实战真题
真题 3:多任务顺序执行
题目描述:在文件上传功能中,需要依次完成文件压缩、加密、上传三个步骤,如何确保任务按顺序执行?
解决方案:使用 CountDownLatch
来实现任务间的同步。
// 创建CountDownLatch,初始计数值为1
CountDownLatch latch1 = new CountDownLatch(1);
// 创建另一个CountDownLatch,初始计数值为1
CountDownLatch latch2 = new CountDownLatch(1); // 提交压缩任务到线程池
executor.submit(() -> { compressFile(); // 执行文件压缩操作latch1.countDown(); // 压缩任务完成,计数值减1
});// 提交加密任务到线程池
executor.submit(() -> { try {latch1.await(); // 等待压缩任务完成(计数值变为0)encryptFile(); // 执行文件加密操作latch2.countDown(); // 加密任务完成,计数值减1} catch (InterruptedException e) {e.printStackTrace();}
});// 提交上传任务到线程池
executor.submit(() -> { try {latch2.await(); // 等待加密任务完成(计数值变为0)uploadFile(); // 执行文件上传操作} catch (InterruptedException e) {e.printStackTrace();}
});
回答话术:
“我会使用 CountDownLatch
实现任务同步。首先创建两个 CountDownLatch
,初始计数值都为 1,分别控制压缩→加密、加密→上传的依赖关系。
提交任务时,压缩任务完成后调用 latch1.countDown()
释放信号;加密任务通过 latch1.await()
等待压缩完成,完成后再释放 latch2
;最后上传任务等待 latch2
信号。这样通过计数器的增减,强制任务按顺序执行。
若任务失败,可在每个任务中捕获异常并记录,后续通过 CyclicBarrier
或自定义状态机实现重试逻辑。例如,若加密失败,回滚压缩结果并重新执行压缩和加密,确保流程可靠性。”
面试追问:
- 问:如果中间某个任务失败怎么办?
- 答:可以在每个任务中捕获异常,记录失败信息。使用
CyclicBarrier
或自定义状态机来管理任务重试逻辑,确保整体流程的可靠性。
真题 4:任务优先级调度
题目描述:APP 中有高优先级的用户登录任务和低优先级的日志上报任务,如何确保高优先级任务优先执行?
解决方案:使用 PriorityBlockingQueue
来实现任务优先级调度。
// 定义一个实现Runnable和Comparable接口的任务类
class PrioritizedTask implements Runnable, Comparable<PrioritizedTask> { private final int priority; // 任务优先级private final Runnable task; // 具体任务public PrioritizedTask(int priority, Runnable task) {this.priority = priority;this.task = task;}@Overridepublic void run() {task.run(); // 执行具体任务}@Overridepublic int compareTo(PrioritizedTask other) {return Integer.compare(other.priority, this.priority); // 比较任务优先级,倒序排列,高优先级在前}
}// 创建线程池
ExecutorService executor = new ThreadPoolExecutor(5, // 核心线程数10, // 最大线程数30L, // 非核心线程空闲时的存活时间TimeUnit.SECONDS, // 存活时间单位为秒new PriorityBlockingQueue<>() // 优先级队列
);// 提交高优先级的用户登录任务
executor.submit(new PrioritizedTask(1, () -> login()));
// 提交低优先级的日志上报任务
executor.submit(new PrioritizedTask(2, () -> uploadLog()));
回答话术:
“使用 PriorityBlockingQueue
作为线程池的任务队列即可实现。自定义 PrioritizedTask
类,实现 Comparable
接口并重写 compareTo
方法,按优先级倒序排列(高优先级在前)。线程池从队列中获取任务时,始终优先执行优先级高的任务。
相比其他方案,PriorityBlockingQueue
更灵活:它基于堆结构实现,插入和获取任务的时间复杂度为 O (log n),效率较高;同时支持动态调整任务优先级。例如,若有新的高优先级登录任务加入,能立即插队执行,而低优先级的日志上报任务会暂停等待,确保核心业务的响应速度。”
三、线程池与 Android 生命周期管理
真题 5:Activity 中的线程池内存泄漏
题目描述:在 Activity 中使用线程池执行网络请求,旋转屏幕后内存占用持续上升,如何解决?
解决方案:
- 正确关闭线程池:在
Activity
的onDestroy
方法中调用executor.shutdown()
优雅关闭线程池,如需立即关闭可调用executor.shutdownNow()
。 - 使用弱引用:避免
Activity
被任务强引用导致无法回收。
public class MainActivity extends AppCompatActivity {private ExecutorService executor;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);// 创建固定大小的线程池executor = Executors.newFixedThreadPool(5); }@Overrideprotected void onDestroy() {super.onDestroy();executor.shutdown(); // 优雅关闭线程池// 如需立即关闭线程池,可使用executor.shutdownNow();}
}class MyTask implements Runnable {private WeakReference<Activity> activityRef; // 使用弱引用持有Activitypublic MyTask(Activity activity) {activityRef = new WeakReference<>(activity);}@Overridepublic void run() {Activity activity = activityRef.get();if (activity != null) {// 任务逻辑}}
}
回答话术:
“内存泄漏主要因线程池任务持有 Activity 强引用,或线程池未正确关闭。
首先,在 Activity
的 onDestroy
方法中调用 executor.shutdown()
优雅关闭线程池,它会等待已提交任务执行完毕后释放资源;若需立即中断任务,可使用 executor.shutdownNow()
。
其次,避免任务强引用 Activity:将任务类中的 Activity 引用改为 WeakReference
,确保 Activity 被销毁时,任务不再阻止其回收。
最后,建议将线程池与 ViewModel
或 Service
绑定,而非直接关联 Activity。例如,使用 ViewModel
管理长期运行的任务,其生命周期与 Activity 分离,能有效防止因配置变更(如旋转屏幕)导致的内存泄漏问题。”
四、WorkManager 替代方案场景
真题 6:后台任务调度
题目描述:APP 需要在设备充电且 Wi-Fi 连接时,自动备份数据到云端,如何实现?
解决方案:使用 WorkManager
结合 Constraints
来实现后台任务调度。
// 构建任务约束条件
Constraints constraints = new Constraints.Builder() .setRequiresCharging(true) // 设置任务需要设备充电.setRequiredNetworkType(NetworkType.UNMETERED) // 设置任务需要连接非计量网络(如Wi-Fi).build();// 创建一次性工作请求,指定工作类和约束条件
OneTimeWorkRequest backupWorkRequest = new OneTimeWorkRequest.Builder(BackupWorker.class) .setConstraints(constraints).build();// 将工作请求加入WorkManager队列
WorkManager.getInstance(context).enqueue(backupWorkRequest);
回答话术:
“使用 WorkManager
结合 Constraints
能轻松实现。通过 Constraints.Builder
设置条件:setRequiresCharging(true)
确保设备充电,setRequiredNetworkType(NetworkType.UNMETERED)
限制为 Wi-Fi 网络。然后创建 OneTimeWorkRequest
,将备份任务类和约束条件传入,最后加入 WorkManager 队列。
WorkManager 相比 ThreadPoolExecutor
有显著优势:它支持任务重试、延迟执行、系统级调度,且与 Android 生命周期深度集成(如设备重启后任务自动恢复)。而 ThreadPoolExecutor
更专注于任务并发执行,需开发者手动处理调度逻辑和资源管理。因此,对于这种有条件触发、需长期可靠运行的后台任务,WorkManager 是更优选择。”
面试追问:
- 问:WorkManager 与 ThreadPoolExecutor 的区别?
- 答:WorkManager 是 Android 提供的高级任务调度框架,支持任务重试、延迟执行、约束条件等,与系统生命周期紧密集成;而 ThreadPoolExecutor 更专注于任务的并发执行,需要开发者手动处理任务调度和资源管理。
额外具体场景设计:
一、如何设计线程池让 100 个任务最快完成?
1. 核心考点:线程池参数调优与 Android 场景适配
Android 面试中,线程池设计需结合设备 CPU 核心数、任务类型(CPU 密集型 / IO 密集型)以及内存管理要求,避免 OOM 和资源浪费。
关键参数设计:
- 核心线程数(corePoolSize):
- CPU 密集型任务:设为
CPU 核心数 + 1
(充分利用 CPU,避免线程上下文切换损耗),可通过Runtime.getRuntime().availableProcessors()
获取核心数。 - IO 密集型任务:设为
2 * CPU 核心数
(因 IO 等待时线程不占用 CPU,更多线程可提高并发效率)。
- CPU 密集型任务:设为
- 最大线程数(maximumPoolSize):
- 对于 CPU 密集型,通常与核心线程数一致(避免无意义的线程创建);
- 对于 IO 密集型,可适当增大,但需结合任务队列容量避免内存溢出(Android 中建议使用有界队列
ArrayBlockingQueue
,而非无界队列LinkedBlockingQueue
,防止内存暴涨)。
- 任务队列(workQueue):
- 有界队列(如
ArrayBlockingQueue
):更安全,可控制并发量,避免 OOM(Android 内存受限,无界队列可能导致后台任务堆积)。 - 优先级队列(
PriorityBlockingQueue
):若任务有优先级差异,可优先执行高优先级任务。
- 有界队列(如
- 线程存活时间(keepAliveTime):
- 非核心线程空闲时的存活时间,IO 密集型任务可设稍长(如 30s),CPU 密集型可设 0(快速回收空闲线程)。
Android 禁用默认线程池(高频考点):
避免使用 Executors.newFixedThreadPool()
(无界队列导致 OOM)或 newCachedThreadPool()
(最大线程数无限),必须手动创建 ThreadPoolExecutor
,示例:
int cpuCores = Runtime.getRuntime().availableProcessors();
ExecutorService executor = new ThreadPoolExecutor(cpuCores + 1, // corePoolSize(CPU 密集型)2 * cpuCores, // maximumPoolSize(IO 密集型可增大)30L, TimeUnit.SECONDS,// keepAliveTimenew ArrayBlockingQueue<>(100), // 有界队列,容量根据任务量调整new ThreadPoolExecutor.CallerRunsPolicy() // 拒绝策略:任务被调用线程执行,避免丢弃
);
性能优化点:
- 任务轻量化: 避免在任务中执行耗时 UI 操作(需通过
Handler
/runOnUiThread
切换主线程)。 - 复用线程: 线程池复用线程避免频繁创建销毁开销,比
new Thread()
更高效。
二、如何确保前 99 个任务完成后再执行第 100 个任务?
1. 核心考点:同步机制选择(CountDownLatch 或 Future)
Android 面试中,需区分 任务依赖关系 和 线程间同步,常用方案:
方案一:CountDownLatch(最简洁方案)
- 原理:通过计数器(初始值 99)阻塞第 100 个任务,前 99 个任务完成时调用
countDown()
,计数器归零后第 100 个任务继续执行。 - Android 注意点:
- 第 100 个任务需在后台线程执行
latch.await()
,避免阻塞主线程(否则触发 ANR)。 - 任务中避免持有 Activity 强引用,防止内存泄漏(可使用弱引用或静态内部类)。
- 第 100 个任务需在后台线程执行
- 代码示例:
// 初始化 CountDownLatch(计数器 99)
private final CountDownLatch latch = new CountDownLatch(99);// 提交前 99 个任务
for (int i = 0; i < 99; i++) {executor.submit(() -> {// 执行任务...latch.countDown(); // 任务完成,计数器减一});
}// 提交第 100 个任务(依赖前 99 个)
executor.submit(() -> {try {latch.await(); // 阻塞直到计数器归零// 执行第 100 个任务...} catch (InterruptedException e) {Thread.currentThread().interrupt(); // 恢复中断状态}
});
方案二:Future 批量获取结果(适合任务有返回值场景)
- 原理:将前 99 个任务的
Future
存入列表,通过Future.allOf()
等待所有任务完成,再执行第 100 个任务。 - 优势:可处理任务返回值,支持异常处理(如
Future.get()
抛ExecutionException
)。 - 代码示例:
List<Future<?>> futures = new ArrayList<>(99);
for (int i = 0; i < 99; i++) {futures.add(executor.submit(() -> { /* 任务逻辑 */ }));
}// 提交第 100 个任务,等待前 99 个完成
executor.submit(() -> {try {Future.allOf(futures.toArray(new Future[0])).get(); // 阻塞直到所有完成// 执行第 100 个任务...} catch (InterruptedException | ExecutionException e) {// 处理异常}
});
方案对比(面试官可能追问):
方案 | 优点 | 缺点 | 适用场景 |
---|---|---|---|
CountDownLatch | 轻量、无返回值需求 | 仅支持单向等待 | 纯任务依赖场景 |
Future.allOf | 支持返回值、异常处理 | 代码稍复杂 | 任务有结果校验场景 |
三、Android 面试加分项(必答点)
-
线程池生命周期管理:
- 在 Activity/Fragment 的
onDestroy()
中调用executor.shutdown()
,避免后台线程持有上下文导致内存泄漏。 - 若需立即中断任务,调用
shutdownNow()
,并处理InterruptedException
。
- 在 Activity/Fragment 的
-
避免 ANR:
- 所有耗时任务(包括
latch.await()
/Future.get()
)必须在后台线程执行,UI 操作通过Handler
切换回主线程。
- 所有耗时任务(包括
-
替代方案(高级考点):
- 若任务需与 Android 生命周期绑定,可使用
WorkManager
(适合后台任务调度,支持延迟、重试、约束条件)。 - 对于轻量任务,可使用
ExecutorService
+HandlerThread
(自定义线程消息循环)。
- 若任务需与 Android 生命周期绑定,可使用
相关文章:
Android学习总结之线程池篇
一、线程池参数调优实战真题 真题 1:直播 APP 弹幕加载线程池设计 题目描述:直播 APP 需要实时加载弹幕数据(网络请求,IO 密集型),同时渲染弹幕视图(UI 操作需切主线程)࿰…...

OpenCV 中用于背景分割的一个类cv::bgsegm::BackgroundSubtractorLSBP
操作系统:ubuntu22.04 OpenCV版本:OpenCV4.9 IDE:Visual Studio Code 编程语言:C11 算法描述 cv::bgsegm::BackgroundSubtractorLSBP 是 OpenCV 中用于背景分割的一个类,它基于局部样本二进制模式(Local Sample Bina…...

MacOS 上构建 gem5
MacOS 中只存在 python3,但是scons 只认 python,不在 系统中创建 软连接,一个是因为比较难操作;另一个是尽量不要更改系统。所以独立构件python 和scons: 1,安装python 下载源代码: Python S…...

认识中间件-以及两个简单的示例
认识中间件-以及两个简单的示例 什么是中间件一个响应处理中间件老朋友 nest g如何使用为某个module引入全局引入编写逻辑一个日志中间件nest g mi 生成引入思考代码进度什么是中间件 官方文档 中间件是在路由处理程序之前调用的函数。中间件函数可以访问请求和响应对象,以及…...

(五)毛子整洁架构(分布式日志/Redis缓存/OutBox Pattern)
文章目录 项目地址一、结构化日志1.1 使用Serilog1. 安装所需要的包2. 注册服务和配置3. 安装Seq服务 1.2 添加分布式id中间件1. 添加中间件2. 注册服务3. 修改Application的LoggingBehavior 二、Redis缓存2.1 添加缓存1. 创建接口ICaching接口2. 实现ICaching接口3. 注册Cachi…...
SQL:MySQL函数:字符串函数
目录 为什么需要字符串函数? 1️⃣ LENGTH(str) — 这个字符串有几个“字节”? 2️⃣ CHAR_LENGTH(str) — 这个字符串有几个“字符”? 3️⃣ TRIM(str) — 把两边的空格剪掉 4️⃣ REPLACE(str, a, b) — 把 a 替换成 b 使用这些函数时…...
DAY04:Vue.js 指令与事件处理深度解析之从基础到实战
1. 指令系统核心概念 1.1 插值表达式与基础指令 Vue.js 的指令系统是其响应式编程模型的核心,我们首先从最基础的插值表达式开始: <div id"app"><!-- 基础文本插值 --><p>{{ message }}</p><!-- JavaScript 表达…...

大模型微调终极方案:LoRA、QLoRA原理详解与LLaMA-Factory、Xtuner实战对比
文章目录 一、微调概述1.1 微调步骤1.2 微调场景 二、微调方法2.1 三种方法2.2 方法对比2.3 关键结论 三、微调技术3.1 微调依据3.2 LoRA3.2.1 原理3.2.2 示例 3.3 QLoRA3.4 适用场景 四、微调框架4.1 LLaMA-Factory4.2 Xtuner4.3 对比 一、微调概述 微调(Fine-tun…...

云效 MCP Server:AI 驱动的研发协作新范式
作者:黄博文、李晔彬 云效 MCP Server 是什么? 云效 MCP(Model Context Protocol)是阿里云云效平台推出的模型上下文协议标准化接口系统,作为连接 AI 助手与 DevOps 平台的核心桥梁,通过模型上下文协议将…...

Linux常见指令解析(三)
通配符 * *可以匹配任意名称的文件,如: ls * 列出当前目录下的所有非隐藏文件和目录,并展开目录内容 ls *.c 列出当前目录下以.c为结尾的文件 rm -rf * 删除所有非隐藏文件 alias指令 alias指令用于给命令取别名。如: 给ls …...
消息队列如何保证消息可靠性(kafka以及RabbitMQ)
目录 RabbitMQ保证消息可靠性 生产者丢失消息 MQ丢失消息 消费端丢失了数据 Kakfa的消息可靠性 生产者的消息可靠性 Kakfa的消息可靠性 消费者的消息可靠性 RabbitMQ保证消息可靠性 生产者丢失消息 1.事务消息保证 生产者在发送消息之前,开启事务消息随后生…...

HTTP学习
HTTP知识 01. 经典五层模型 应用层 为应用软件提供了很多服务,构建于协议之上。 传输层 数据的传输都是在这层定义的,数据过大分包,分片。 网络层 为数据在节点之间传输创建逻辑链路 数据链路层 通讯实体间建立数据链路连接 物理层 主要作用…...

go语言实现IP归属地查询
效果: 实现代码main.go package mainimport ("encoding/json""fmt""io/ioutil""net/http""os" )type AreaData struct {Continent string json:"continent"Country string json:"country"ZipCode …...
一站式解决技术平台访问难题:合规优化方案助力高效开发(2025 最新)
摘要 在技术开发与协作中,GitHub 等国际平台的访问效率及国内社区的使用体验常受网络环境影响。本文严格遵循网络安全规范,聚焦本地化 DNS 优化、官方镜像加速、浏览器工具提效等合规技术,提供覆盖国内外技术平台的访问优化方案,…...

Android RxJava框架分析:它的执行流程是如何的?它的线程是如何切换的?如何自定义RxJava操作符?
目录 RxJava是什么?为什么使用。RxJava是如何使用的呢?RxJava如何和Retrofit一起使用。RxJava源码分析。 (1)他执行流程是如何的。(2)map(3)线程的切换。 如何自定义RxJava操作符…...

MySQL及线程关于锁的面试题
目录 1.了解过 MySQL 死锁问题吗? 2.什么是线程死锁?死锁相关面试题 2.1 什么是死锁: 2.2 形成死锁的四个必要条件是什么? 2.3 如何避免线程死锁? 3. MySQL 怎么排查死锁问题? 4.Java线上死锁问题如…...
前端vue+elementplus实现上传通用组件
上传组件: <template><div class"upload-file"><el-uploadmultiple:action"uploadFileUrl":before-upload"handleBeforeUpload":file-list"fileList":limit"limit":on-error"handleUploadE…...
联合类型的逻辑或关系与类型保护
在 TypeScript 中,联合类型(Union Types)是一种强大的类型工具,它允许一个变量可以是几种不同类型中的一种。联合类型通过逻辑“或”关系(|)连接多个类型。这种类型的灵活性使得我们能够处理多样化的数据输…...
spring boot3.0自定义校验注解:文章状态校验示例
文章目录 Spring Boot 自定义校验注解:状态校验示例一、创建 State 注解步骤:1. 创建自定义注解:2. 实现校验逻辑: 二、 实现自定义校验步骤:1. 在实体类中使用自定义校验注解 State:2. 添加 State 注解: 总…...
神经网络是如何工作的
人工智能最核心的技术之一,就是神经网络(Neural Networks)。但很多初学者会觉得它是个黑盒:为什么神经网络能识别图片、翻译语言,甚至生成文章? 本文用图解最小代码实现的方式,带你深入理解&am…...

【工作记录】crmeb后端项目打开、运行
1、下载代码 1)安装git 不再详述 2)git拉代码 项目地址如下,在vscode-分支中拉代码 # 克隆项目 git clone https://gitee.com/ZhongBangKeJi/crmeb_java/ 截图如下是已经成功拉下来 注意安装对应版本 2、maven配置 安装配置见&#x…...

智能手表测试计划文档(软/硬件)
📄 智能手表测试计划文档(软/硬件) 项目名称:Aurora Watch S1 文档编号:AW-S1-QA-TP-001 编制日期:2025-xx-xx 版本:V1.0 编写人:xxx(测试主管) 一、测试目标…...
DeepSeek:开启能源领域智能化变革新时代
目录 一、DeepSeek 与能源领域变革的邂逅1.1 DeepSeek 在人工智能领域的地位与特点1.2 能源行业面临的挑战与变革需求1.3 DeepSeek 在能源领域应用的重要性和意义 二、能源政策解读与科普新助手2.1 能源政策解读的深度变革2.2 能源科普的创新使者 三、能源项目可行性分析新利器…...
红黑树算法笔记(二)性能对比实验
文章目录 1. 实验目标2. 对比数据结构3. 性能指标3.1 时间性能指标3.2 空间性能指标3.3 其他性能指标 4. 测试场景4.1 数据集特性变化4.2 操作模式变化4.3 环境因素变化 5. 实验设计5.1 基准测试设计5.1.1 CRUD性能基准测试5.1.2 混合负载测试5.1.3 范围查询测试 5.2 特殊场景测…...
Nlog适配达梦数据库进行日志插入
前言 原来使用的是SQLServer数据库,使用Nlog很流畅,没有什么问题。现在有个新项目需要使用麒麟操作系统和达梦数据库,业务流程开发完成之后发现Nlog配置文件中把数据库连接内容修改之后不能执行插入操作。 原Nlog.config配置 <?xml ve…...

k8s监控方案实践(三):部署与配置Grafana可视化平台
k8s监控方案实践(三):部署与配置Grafana可视化平台 文章目录 k8s监控方案实践(三):部署与配置Grafana可视化平台一、Grafana简介1. 什么是Grafana?2. Grafana与Prometheus的关系3. Grafana应用场…...

嵌入式系统架构验证工具:AADL Inspector v1.10 全新升级
软件架构建模与早期验证是嵌入式应用的关键环节。架构分析与设计语言(AADL)是专为应用软件及执行平台架构模型设计的语言,兼具文本与图形化的双重特性。AADL Inspector是一款轻量级的独立工具: 核心处理能力包括 √ 支持处理AA…...

STM32-模电
目录 一、MOS管 二、二极管 三、IGBT 四、运算放大器 五、推挽、开漏、上拉电阻 一、MOS管 1. MOS简介 这里以nmos管为例,注意箭头方向。G门极/栅极,D漏极,S源极。 当给G通高电平时,灯泡点亮,给G通低电平时&a…...

华为云Flexus+DeepSeek征文|从开通到应用:华为云DeepSeek-V3/R1商用服务深度体验
前言 本文章主要讲述在华为云ModelArts Studio上 开通DeepSeek-V3/R1商用服务的流程,以及开通过程中的经验分享和使用感受帮我更多开发者,在华为云平台快速完成 DeepSeek-V3/R1商用服务的开通以及使用入门注意:避免测试过程中出现部署失败等问…...

鸿蒙NEXT开发动画案例5
1.创建空白项目 2.Page文件夹下面新建Spin.ets文件,代码如下: /*** TODO SpinKit动画组件 - Pulse 脉冲动画* author: CSDN—鸿蒙布道师* since: 2024/05/09*/ ComponentV2 export struct SpinFive {// 参数定义Require Param spinSize: number 48;Re…...