学习笔记
📚AI大数据相关
00. Ai大数据模型
模型整理
使用 DeepSeek 通用公式
学会这8招,让DeepSeek变得超好用!
大数据购物分析选优
Windows和Ubuntu部署DeepSeek性能差距
本地部署 Ollam+DeepSeek 探索爬坑
模型对比测试
React Native 和 native 半屏弹窗
React Native Modal + WebView
01. Ai 提示词
00. 🌟提示词黄金公式:让 AI 秒变专家的通用指令框架
01. 🚀 AI短篇小说创作:大神级实操指南与提示词模板
02. Gemini专用Agent:今日头条硬核技术博主
03. Gemini专用Agent:小红书金牌育儿养生博主
04. Gemini专业Agent:全栈架构导师 & 终极面试官
05. Gemini专业Agent:资深私募操盘手 & 首席证券分析师
06. 🤖IDE&&CLI使用Agent:单元测试
07. 高度自主化开发提示词
Java基础相关
JVM内存模型及线程空间
动态代理
java并发编程
Java基础知识
Java中Future
Java中9种常见的CMS GC问题分析与解决
移动端相关
杂乱整理
HarmonyOS 鸿蒙开发知识
ArkTS中如何自定义组件和复用统一样式
Android开发相关
WebView设置圆角
📄 Android 实时屏幕共享技术方案文档
Android线程与线程池全面解析:从使用到源码剖析
🔥 RecyclerView全面解析:从架构原理到性能优化与面试指南
🔥 OkHttp全面解析:从架构原理到性能优化与面试指南
🔥 Retrofit全面解析:从设计架构与核心思想到常见问题
开发工具相关
Git cmd学习整理
Markdown用法大全集
【2023年12月】工作常用
Git如何单独合并某次提交到另一个分支
Debina/Ubuntu安装Node环境
🐳 Docker 命令行与 Docker Compose 全面指南
使用Podman来替代Docker
中文注释提交git后变成"\u0087\u0079..."问题
macOS 容器化工具深度解析:Docker Desktop、OrbStack、Podman 及其他替代方案对比
前端开发相关
Node+TypeScript相关记录
TypeScript 读写 MariaDB
Node TypeScript项目 token生成、管理及拦截校验的实现
TypeScript+Express创建和实现一个服务示例
Express接口处理器抽取注册方式
Express 实现 RESTful API
创建 TypeScript Express 项目,并配置直接用 npm start 运行
TypeScript + Express 实现文件下载接口
export 和 export default的区别
TypeScript+Express 实现用户注册和登录接口
TypeScript 和 JavaScript 中,`===` 和 `==`
CSS中的尺寸大小标准
小程序px和rpx
使用Python快速处理Excel的合并拆分
读书写作相关
一些句子01
李敖语录
罗翔老师的一些经典语句
周易相关知识
周易是对自然描述还是为自然立法
40句落寞诗词,穿透柔魂弱魄
杂玩整理
黑苹果睿频问题
基于纯Linux自己部署Nas构思
Ubuntu换源
Ubuntu挂载tf卡
Ubuntu运行Docker报错
Ubuntu安装运行Docker报错处理
官方镜像安装Docker
Docker 设置root dir 切换数据到其他存储位置
systemctl stop docker 报错
NextCloud安装ffmpeg 显示视频缩略图
Docker源不生效解决方式
Docker源不生效解决方式II——搭建docker-hub镜像
搞定群晖总Docker部署gitea启用ssh协议
MacOS一键安装命令软件列表
群晖ssl证书目录
Android通过ADB命令播放视频
[完成] 群晖自动更新https证书项目
Linux设备整机限速
Linux限速2
Gitea部署Runner服务
精选网站
有声主播知识
学习笔记
有声主播入门到进阶
有声主播新手的入门练手内容推荐
DeepSeek分析喜马拉雅旗下 喜播平台 的有声主播培训
录书设备资料1
典故专辑资料整理
0B. 脚本和大纲
0A. 前置准备资料
临时
Linux下对设备进行限速
C++ 学习
01 Android开发学习C++
其他资料分组
【面试相关】💡 面试后、Offer前,可以主动了解和确认的信息
【工作经验】会计师事务所工作中如何有效管理和规避法律风险
职业发展规划
-
+
home page
Android线程与线程池全面解析:从使用到源码剖析
## 1 线程基础 ### 1.1 进程与线程的区别 **进程**是操作系统进行资源分配和调度的基本单位,每个Android应用运行在一个独立的进程中,系统为其分配独立的内存空间。**线程**是进程内的执行单元,是CPU调度和执行的基本单位,一个进程可以包含多个线程,这些线程共享进程的内存空间和资源。 在Android系统中,理解进程与线程的区别至关重要。当应用启动时,系统会为其创建一个主线程(也称为UI线程),负责处理用户交互和界面更新。如果需要执行耗时操作,则需要创建子线程来处理,以避免阻塞主线程。 ### 1.2 线程的生命周期与状态 Java线程在其生命周期中会经历多种状态,掌握这些状态及其转换条件对正确管理线程至关重要: - **New(新建)**:线程对象被创建但尚未启动,此时`start()`方法还未被调用。 - **Runnable(可运行)**:线程已启动并正在执行或准备执行,等待CPU分配时间片。注意:在Java线程模型中,Running(运行中)和Ready(就绪)都被归为此状态。 - **Blocked(阻塞)**:线程试图获取一个内部对象锁(不是Java.util.concurrent锁)而该锁正被其他线程持有。 - **Waiting(等待)**:线程进入等待状态,需要其他线程做出特定动作(通知或中断),通过`Object.wait()`、`Thread.join()`或`LockSupport.park()`方法进入。 - **Timed Waiting(定时等待)**:与Waiting状态类似,但可以在指定时间后自动返回,通过`Thread.sleep()`、`Object.wait(timeout)`、`Thread.join(timeout)`等方法进入。 - **Terminated(终止)**:线程已执行完毕或因异常退出,不可再次启动。 **表:Java线程状态转换及触发方法** | 状态转换 | 触发方法 | | :----------------------- | :----------------------------------------------------------- | | New → Runnable | `start()`方法被调用 | | Runnable → Blocked | 尝试获取已被其他线程持有的对象锁 | | Runnable → Waiting | 调用`Object.wait()`、`Thread.join()`或`LockSupport.park()` | | Runnable → Timed Waiting | 调用`Thread.sleep()`、`Object.wait(timeout)`等带超时参数的方法 | | 阻塞/等待状态 → Runnable | 相应的唤醒条件满足(如锁可用、超时时间到、被通知/中断) | | Runnable → Terminated | `run()`方法执行完成或抛出未捕获异常 | ### 1.3 线程的创建方式 在Java中,有几种创建线程的方式,每种方式各有适用场景: - **继承Thread类**:通过扩展Thread类并重写其`run()`方法 - **实现Runnable接口**:实现Runnable接口并将实例作为参数传递给Thread构造函数 - **使用Callable和Future**:可以返回结果和抛出异常,配合ExecutorService使用 **实现Runnable接口相比继承Thread类更具优势**,因为它避免了Java单继承的限制,允许线程类继承其他类;同时更适合多个线程共享同一资源的情况,因为Runnable对象可以被多个线程共享。而Callable与Future的组合则提供了更强大的功能,允许线程返回执行结果、捕获异常,并支持取消操作。 ### 1.4 线程安全 当多个线程同时访问共享资源时,如果不采取适当的同步措施,可能会导致数据不一致、脏读等问题,这就是**线程安全**问题。Android提供了多种机制来保证线程安全: - **synchronized关键字**:可以修饰方法或代码块,确保同一时刻只有一个线程能执行该段代码。它基于对象监视器锁实现,支持可重入性。 - **ReentrantLock**:比synchronized更灵活的锁机制,支持公平锁、非公平锁,提供 Condition 条件变量,可以精确控制线程的等待与唤醒。 - **volatile关键字**:保证变量的可见性(一个线程修改后其他线程立即可见)和禁止指令重排序,但不保证原子性。 - **原子类**:如AtomicInteger、AtomicLong等,基于CAS(Compare-And-Swap)操作实现,提供原子性的读写操作。 - **不可变对象**:创建后状态不可改变的对象,如String、Integer等,天然线程安全。 **死锁**是多线程编程中常见的问题,指两个或更多线程互相等待对方持有的资源,导致所有线程都无法继续执行。避免死锁的策略包括:避免嵌套锁、按固定顺序获取锁、使用尝试获取锁的机制(`tryLock()`)以及设置超时时间。 ## 2 Android线程模型 ### 2.1 主线程(UI线程)与ANR 在Android应用中,**主线程**(也称作UI线程)是应用启动时系统自动创建的主要线程,它承担着多项关键职责: - 处理所有用户交互事件(点击、触摸、滚动等) - 绘制和更新用户界面 - 分发事件给相应的UI组件 - 执行Activity、Fragment等组件的生命周期回调 Android系统规定**所有UI操作必须在主线程中执行**,如果在子线程中直接更新UI,会抛出`CalledFromWrongThreadException`。 **ANR(Application Not Responding)** 是Android系统中重要的监控机制,当应用的主线程被阻塞时间过长时,系统会弹出ANR对话框。触发ANR的主要条件有: - **输入事件无响应**:5秒内未能响应输入事件(如按键或触摸) - **BroadcastReceiver超时**:10秒内未能完成BroadcastReceiver的执行 - **Service无响应**:20秒内前台Service未执行完毕(某些情况) 避免ANR的策略是将耗时操作(如网络请求、复杂计算、数据库读写等)移出主线程,放到后台线程中执行,待完成后通过特定机制通知主线程更新UI。 ### 2.2 Handler机制原理 Handler机制是Android系统线程间通信的核心框架,特别是用于从子线程向主线程传递消息和任务。它由四个核心组件构成: - **Message**:需要传递的消息对象,可以携带数据、任务和目标Handler - **MessageQueue**:消息队列,按时间顺序存储待处理的Message,每个线程只有一个 - **Looper**:消息循环器,不断从MessageQueue中取出消息并分发给对应的Handler - **Handler**:消息的处理者和发送者,负责发送消息和处理消息 **Looper.prepare()** 方法会创建当前线程的Looper实例,并存储在ThreadLocal中,确保每个线程只有一个Looper。**Looper.loop()** 方法启动一个无限循环,不断从MessageQueue中取出消息,然后调用`msg.target.dispatchMessage(msg)`将消息分发给对应的Handler处理。 主线程的Looper是在ActivityThread的`main()`方法中创建的,这也是为什么主线程自动具有消息循环能力的原因。 **Handler的工作流程**包括发送消息和处理消息两个部分。发送消息可以通过`sendMessage()`、`sendMessageDelayed()`、`post(Runnable)`等方法,这些方法最终都会将消息或Runnable封装成Message并放入MessageQueue。处理消息时,Handler的`dispatchMessage()`方法会根据情况调用`handleMessage()`或直接执行Runnable任务。 **避免Handler内存泄漏**是关键问题,因为Handler如果作为非静态内部类,会隐式持有外部类(如Activity)的引用。解决方案包括:使用静态内部类+弱引用(WeakReference),并在Activity销毁时调用`handler.removeCallbacksAndMessages(null)`清除所有待处理消息。 HandlerThread是Android提供的一种特殊线程,它内部已经创建了Looper和MessageQueue,可以直接用于创建Handler,简化了带有消息循环能力的后台线程的创建。 ### 2.3 AsyncTask、IntentService及其替代方案 **AsyncTask**是Android早期提供的轻量级异步任务类,它封装了线程池和Handler,提供了`onPreExecute()`、`doInBackground()`、`onProgressUpdate()`和`onPostExecute()`等方法,分别在不同阶段和线程执行。但由于其存在生命周期问题、内存泄漏风险、并发控制不一致等缺点,现已被官方标记为废弃(Deprecated)。 **IntentService**是一种特殊的Service,它内部使用工作线程串行处理所有Intent请求,在处理完所有请求后会自动停止。但由于与Service组件本身的限制以及Android对后台执行的限制,也已被官方标记为废弃。 **现代Android开发中的替代方案**包括: - **Kotlin协程**:轻量级并发解决方案,通过挂起函数实现非阻塞式异步编程,结合`viewModelScope`和`lifecycleScope`提供自动的生命周期管理 - **WorkManager**:用于处理可延迟的后台任务,保证任务最终会执行,适合不需要立即执行的持久化任务 - **线程池+LiveData/ViewModel**:结合Java标准线程池和Android架构组件,提供可感知生命周期的后台任务管理 ## 3 线程池详解 ### 3.1 为什么需要线程池 在Android开发中,直接创建线程虽然简单,但存在诸多问题,而线程池提供了专业的解决方案: - **资源消耗大**:每个Java线程默认占用约1MB栈内存,大量线程会消耗宝贵的内存资源,可能导致OOM(内存溢出) - **创建销毁开销大**:线程的创建和销毁需要调用系统内核函数,是昂贵的操作,频繁操作会影响性能 - **管理困难**:直接创建的线程分散在代码各处,难以统一管理、监控和取消 **线程池的优势**体现在以下几个方面: - **资源复用**:通过复用已创建的线程,减少线程创建和销毁的开销 - **控制并发数**:通过参数控制并发线程数量,防止线程过多导致系统过载 - **管理任务队列**:提供任务队列缓冲突发的大量任务,平滑系统负载 - **提供监控**:通过钩子方法监控任务执行状态,便于问题排查和性能优化 ### 3.2 线程池核心参数 ThreadPoolExecutor是Java线程池的核心实现类,其构造函数包含7个核心参数,这些参数共同决定了线程池的行为特性: 1. **corePoolSize(核心线程数)**:线程池中长期保持的线程数量,即使这些线程处于空闲状态也不会被销毁(除非设置`allowCoreThreadTimeOut`为true) 2. **maximumPoolSize(最大线程数)**:线程池允许创建的最大线程数量,当工作队列满时,线程池会创建新线程直到达到此限制 3. **keepAliveTime(空闲线程存活时间)**:当线程数量超过核心线程数时,多余的空闲线程在终止前等待新任务的最长时间 4. **unit(时间单位)**:keepAliveTime参数的时间单位,如TimeUnit.SECONDS、TimeUnit.MILLISECONDS等 5. **workQueue(工作队列)**:用于保存等待执行的任务的阻塞队列,常见的有LinkedBlockingQueue、ArrayBlockingQueue、SynchronousQueue等 6. **threadFactory(线程工厂)**:用于创建新线程的工厂,可以自定义线程名称、优先级、守护状态等 7. **handler(拒绝策略)**:当工作队列已满且线程数已达到最大值时,用于处理新提交任务的策略 **表:线程池参数配置策略** | 参数 | CPU密集型任务 | IO密集型任务 | 混合型任务 | | :-------------- | :---------------------- | :-------------------- | :--------------- | | corePoolSize | CPU核数 + 1 | 2 * CPU核数 | 根据任务比例调整 | | maximumPoolSize | corePoolSize + 1 或相同 | corePoolSize * 2 | 适中扩展 | | workQueue | 有界队列(大小适中) | 有界或无界队列 | 有界队列 | | keepAliveTime | 较短时间(如30-60秒) | 较长时间(如1-2分钟) | 适中时间 | ### 3.3 线程池工作机制 线程池的任务处理遵循一套精心设计的流程,如下图所示:  具体来说,当有新任务提交时,线程池的处理逻辑如下: 1. 如果当前线程数小于核心线程数,线程池会创建新的核心线程执行任务 2. 如果当前线程数达到或超过核心线程数,任务会被放入工作队列等待执行 3. 如果工作队列已满,且当前线程数小于最大线程数,线程池会创建新的非核心线程执行任务 4. 如果工作队列已满,且当前线程数达到最大线程数,新任务会被拒绝执行,根据拒绝策略处理 ### 3.4 常见的线程池类型 Executors类提供了一系列工厂方法用于创建常见配置的线程池,但在生产环境中需要谨慎使用,因为某些配置可能存在资源耗尽的风险: - **FixedThreadPool**:固定大小的线程池,使用无界队列LinkedBlockingQueue,适用于负载较重的服务器,需要限制线程数量的场景 - **CachedThreadPool**:可根据需要创建新线程的线程池,使用SynchronousQueue,最大线程数为Integer.MAX_VALUE,适用于执行很多短期异步任务的小程序或负载较轻的服务器 - **SingleThreadExecutor**:单线程线程池,使用无界队列,保证所有任务按顺序执行,适用于需要顺序执行任务的场景 - **ScheduledThreadPool**:支持定时及周期性任务执行的线程池,适用于需要多个后台线程执行周期任务的场景 **阿里Java开发规约不建议直接使用Executors创建线程池**,因为FixedThreadPool和SingleThreadExecutor使用的无界队列可能导致内存溢出,而CachedThreadPool的最大线程数设置过大可能导致线程数量失控。推荐的方式是**直接通过ThreadPoolExecutor构造函数创建线程池**,这样可以明确指定所有参数,避免潜在风险。 ## 4 线程池源码解析 ### 4.1 ThreadPoolExecutor执行流程 ThreadPoolExecutor是Java线程池的核心实现,其设计精巧且高效。理解其源码对深入掌握线程池工作机制至关重要。 **ctl变量**是ThreadPoolExecutor的核心控制状态,它是一个AtomicInteger类型的原子变量,巧妙地将线程池状态和线程数量封装在一个int值中: ```java private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0)); // 高3位存储线程池状态,低29位存储线程数量 private static final int COUNT_BITS = Integer.SIZE - 3; private static final int CAPACITY = (1 << COUNT_BITS) - 1; // 线程池状态 private static final int RUNNING = -1 << COUNT_BITS; private static final int SHUTDOWN = 0 << COUNT_BITS; private static final int STOP = 1 << COUNT_BITS; private static final int TIDYING = 2 << COUNT_BITS; private static final int TERMINATED = 3 << COUNT_BITS; ``` 线程池状态变迁如下:RUNNING → SHUTDOWN → STOP → TIDYING → TERMINATED **execute方法**是ThreadPoolExecutor的核心方法,负责处理任务提交: ```java public void execute(Runnable command) { if (command == null) throw new NullPointerException(); int c = ctl.get(); // 阶段1:当前线程数小于核心线程数 if (workerCountOf(c) < corePoolSize) { if (addWorker(command, true)) return; c = ctl.get(); } // 阶段2:线程池处于RUNNING状态且任务成功入队 if (isRunning(c) && workQueue.offer(command)) { int recheck = ctl.get(); // 再次检查状态,如果非RUNNING则移除任务并执行拒绝策略 if (!isRunning(recheck) && remove(command)) reject(command); // 如果线程数为0,则创建非核心线程(保证任务能被处理) else if (workerCountOf(recheck) == 0) addWorker(null, false); } // 阶段3:队列已满,尝试创建非核心线程 else if (!addWorker(command, false)) // 创建失败(超过最大线程数),执行拒绝策略 reject(command); } ``` 这个方法清晰地体现了线程池的三层处理逻辑:先尝试创建核心线程,然后尝试入队,最后尝试创建非核心线程,如果都失败则执行拒绝策略。 ### 4.2 Worker机制 ThreadPoolExecutor中的工作线程被封装在**Worker内部类**中,它继承自AbstractQueuedSynchronizer并实现了Runnable接口: ```java private final class Worker extends AbstractQueuedSynchronizer implements Runnable { final Thread thread; // 真正执行任务的线程 Runnable firstTask; // 初始任务,可能为null Worker(Runnable firstTask) { setState(-1); // 禁止中断直到runWorker this.firstTask = firstTask; this.thread = getThreadFactory().newThread(this); } public void run() { runWorker(this); // 委托给外部类的runWorker方法 } // ... 其他方法省略 } ``` Worker使用AQS实现了一个简单的不可重入锁,主要用于中断控制。state=0表示锁未被占用,state=1表示锁已被占用。 **runWorker方法**是工作线程执行的核心循环: ```java final void runWorker(Worker w) { Thread wt = Thread.currentThread(); Runnable task = w.firstTask; w.firstTask = null; w.unlock(); // 允许中断 boolean completedAbruptly = true; try { // 循环获取任务:首先执行firstTask,然后从队列中获取 while (task != null || (task = getTask()) != null) { w.lock(); // 如果线程池正在停止,确保线程被中断 if ((runStateAtLeast(ctl.get(), STOP) || (Thread.interrupted() && runStateAtLeast(ctl.get(), STOP))) && !wt.isInterrupted()) wt.interrupt(); try { beforeExecute(wt, task); // 钩子方法 Throwable thrown = null; try { task.run(); // 执行任务 } catch (RuntimeException x) { thrown = x; throw x; } catch (Error x) { thrown = x; throw x; } catch (Throwable x) { thrown = x; throw new Error(x); } finally { afterExecute(task, thrown); // 钩子方法 } } finally { task = null; w.completedTasks++; w.unlock(); } } completedAbruptly = false; } finally { // 处理工作线程退出 processWorkerExit(w, completedAbruptly); } } ``` runWorker方法通过while循环不断从任务队列中获取任务并执行,直到getTask()返回null,此时工作线程会正常退出。 **getTask方法**负责从工作队列中获取任务,它实现了线程池的keepAliveTime机制: ```java private Runnable getTask() { boolean timedOut = false; // 上次poll是否超时 for (;;) { int c = ctl.get(); int rs = runStateOf(c); // 检查线程池状态和队列状态 if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) { decrementWorkerCount(); return null; } int wc = workerCountOf(c); // 判断是否允许超时退出:允许核心线程超时 或 当前线程数大于核心线程数 boolean timed = allowCoreThreadTimeOut || wc > corePoolSize; // 检查是否需要减少工作线程数 if ((wc > maximumPoolSize || (timed && timedOut)) && (wc > 1 || workQueue.isEmpty())) { if (compareAndDecrementWorkerCount(c)) return null; continue; } try { // 根据timed选择poll(带超时)或take(阻塞) Runnable r = timed ? workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) : workQueue.take(); if (r != null) return r; timedOut = true; // 获取任务超时 } catch (InterruptedException retry) { timedOut = false; } } } ``` getTask方法通过timed变量控制工作线程的退出策略。当timed为true时,使用poll方法等待keepAliveTime时间,超时后返回null导致工作线程退出;当timed为false时,使用take方法无限期等待,保证核心线程常驻(除非设置allowCoreThreadTimeOut为true)。 ## 5 高频面试题及答案 ### 5.1 为什么开发中要使用线程池,而不是直接创建线程? **参考答案**:使用线程池相比直接创建线程有以下几个核心优势: 1. **降低资源消耗**:通过线程复用,减少频繁创建和销毁线程带来的性能开销 2. **提高响应速度**:任务到达时可以直接使用已有的线程,无需等待线程创建 3. **提高线程可管理性**:线程是稀缺资源,使用线程池可以统一分配、调优和监控 4. **控制并发数量**:通过参数限制最大线程数,防止线程过多导致系统崩溃 5. **提供任务队列**:缓冲突发的大量任务,平滑系统负载,避免任务丢失 直接创建线程在高并发场景下会导致线程数量急剧增加,消耗大量内存和CPU资源,可能引发OOM和频繁的上下文切换,降低系统吞吐量。 ### 5.2 ThreadPoolExecutor有哪些核心参数?各自的作用是什么? **参考答案**:ThreadPoolExecutor包含7个核心参数: 1. **corePoolSize(核心线程数)**:线程池中长期保持的线程数量,即使空闲也不会被销毁(除非设置allowCoreThreadTimeOut) 2. **maximumPoolSize(最大线程数)**:线程池允许创建的最大线程数量,当队列满时会创建新线程直到达到此限制 3. **keepAliveTime(空闲线程存活时间)**:非核心线程空闲时的存活时间,超时后会被回收 4. **unit(时间单位)**:keepAliveTime的时间单位 5. **workQueue(工作队列)**:用于保存等待执行的任务的阻塞队列 6. **threadFactory(线程工厂)**:用于创建新线程,可以自定义线程名称、优先级等 7. **handler(拒绝策略)**:当线程池和队列都已满时,处理新提交任务的策略 这些参数共同决定了线程池的行为特性,需要根据任务类型(CPU密集型、IO密集型、混合型)合理配置。 ### 5.3 线程池的任务处理流程是怎样的? **参考答案**:线程池处理任务的核心流程如下: 1. 当有新任务提交时,首先判断当前线程数是否小于核心线程数,如果是则创建新的核心线程执行任务 2. 如果当前线程数已达到或超过核心线程数,则尝试将任务放入工作队列 3. 如果工作队列已满,则判断当前线程数是否小于最大线程数,如果是则创建新的非核心线程执行任务 4. 如果线程数已达到最大值且队列已满,则执行拒绝策略 这个过程体现了线程池的优先级:**核心线程 > 工作队列 > 非核心线程 > 拒绝策略**。需要注意的是,在创建新线程时,无论是核心线程还是非核心线程,都是通过addWorker方法创建的,区别在于第二个参数(core参数)不同。 ### 5.4 什么是线程池中的核心线程?核心线程与非核心线程的区别? **参考答案**:核心线程是线程池中长期保持的线程,即使处于空闲状态也不会被销毁(除非设置allowCoreThreadTimeOut为true),是线程池处理任务的"常驻力量"。核心线程与非核心线程的主要区别体现在: 1. **创建时机不同**:核心线程在任务提交时创建(当线程数小于corePoolSize时),非核心线程在工作队列满且线程数小于maximumPoolSize时创建 2. **存活策略不同**:核心线程默认常驻(除非超时设置),非核心线程空闲超过keepAliveTime会被回收 3. **数量限制不同**:核心线程数量由corePoolSize限制,非核心线程数量由maximumPoolSize - corePoolSize决定 在ThreadPoolExecutor的实现中,核心线程和非核心线程都是Worker对象,它们的区别仅在于创建时的core参数不同,这影响了线程的回收策略。 ### 5.5 线程池的拒绝策略有哪些? **参考答案**:当线程池和工作队列都已满时,新提交的任务会触发拒绝策略。JDK提供了4种默认策略: 1. **AbortPolicy(默认)**:直接抛出RejectedExecutionException异常,中断任务提交 2. **CallerRunsPolicy**:让提交任务的线程(如主线程)自己执行该任务 3. **DiscardPolicy**:默默丢弃被拒绝的任务,不做任何处理 4. **DiscardOldestPolicy**:丢弃工作队列中最旧的任务(队首任务),然后尝试重新提交当前任务 除了使用内置策略,也可以实现RejectedExecutionHandler接口来自定义拒绝策略,如将拒绝的任务持久化到磁盘或记录日志以便后续处理。 ### 5.6 如何合理配置线程池参数? **参考答案**:合理配置线程池参数需要考虑任务类型、系统资源和业务需求: - **CPU密集型任务**:线程数不宜过多,通常设置为CPU核数 + 1,避免过多线程导致频繁的上下文切换 - **IO密集型任务**:线程数可以设置较多,通常为2 * CPU核数,因为线程大部分时间在阻塞等待IO - **混合型任务**:根据任务中CPU密集和IO密集的比例调整,也可以考虑拆分任务到不同的线程池 其他考虑因素包括:任务优先级、任务执行时间、任务间的依赖关系等。对于需要精确控制的场景,建议通过压测确定最优参数。 ### 5.7 shutdown()和shutdownNow()的区别? **参考答案**:这两个方法都用于关闭线程池,但行为有所不同: - **shutdown()**:平缓关闭线程池,不再接受新任务,但会处理已提交的任务(包括队列中的任务)。调用后线程池状态变为SHUTDOWN。 - **shutdownNow()**:立即关闭线程池,尝试中断所有正在执行的任务,不再处理队列中等待的任务,返回等待执行的任务列表。调用后线程池状态变为STOP。 优雅关闭线程池的常见做法是:先调用shutdown(),然后调用awaitTermination()等待指定时间,如果仍有任务未完成,再调用shutdownNow()强制关闭。 ### 5.8 为什么阿里Java规约不建议使用Executors创建线程池? **参考答案**:阿里Java开发规约不建议使用Executors创建线程池的主要原因在于其隐藏了线程池的关键参数,可能导致资源耗尽风险: 1. **FixedThreadPool和SingleThreadExecutor**:使用无界队列LinkedBlockingQueue(默认容量Integer.MAX_VALUE),可能堆积大量任务导致内存溢出 2. **CachedThreadPool和ScheduledThreadPool**:最大线程数设为Integer.MAX_VALUE,可能创建大量线程导致内存和CPU资源耗尽 推荐的方式是**直接通过ThreadPoolExecutor构造函数创建线程池**,明确指定所有参数,特别是使用有界队列和合理的拒绝策略,这样可以避免资源耗尽问题,提高系统稳定性。
嘿手大叔
Nov. 11, 2025, 8:09 p.m.
转发文档
Collection documents
Last
Next
手机扫码
Copy link
手机扫一扫转发分享
Copy link
Markdown文件
share
link
type
password
Update password