📚 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个核心参数,这些参数共同决定了线程池的行为特性:
- corePoolSize(核心线程数):线程池中长期保持的线程数量,即使这些线程处于空闲状态也不会被销毁(除非设置allowCoreThreadTimeOut为true)
- maximumPoolSize(最大线程数):线程池允许创建的最大线程数量,当工作队列满时,线程池会创建新线程直到达到此限制
- keepAliveTime(空闲线程存活时间):当线程数量超过核心线程数时,多余的空闲线程在终止前等待新任务的最长时间
- unit(时间单位):keepAliveTime参数的时间单位,如TimeUnit.SECONDS、TimeUnit.MILLISECONDS等
- workQueue(工作队列):用于保存等待执行的任务的阻塞队列,常见的有LinkedBlockingQueue、ArrayBlockingQueue、SynchronousQueue等
- threadFactory(线程工厂):用于创建新线程的工厂,可以自定义线程名称、优先级、守护状态等
- handler(拒绝策略):当工作队列已满且线程数已达到最大值时,用于处理新提交任务的策略
表:线程池参数配置策略
| 参数 | CPU密集型任务 | IO密集型任务 | 混合型任务 |
|---|---|---|---|
| corePoolSize | CPU核数 + 1 | 2* CPU核数 | 根据任务比例调整 |
| maximumPoolSize | corePoolSize + 1 或相同 | corePoolSize* 2 | 适中扩展 |
| workQueue | 有界队列(大小适中) | 有界或无界队列 | 有界队列 |
| keepAliveTime | 较短时间(如30-60秒) | 较长时间(如1-2分钟) | 适中时间 |
3.3 线程池工作机制
线程池的任务处理遵循一套精心设计的流程,如下图所示:
具体来说,当有新任务提交时,线程池的处理逻辑如下:
- 如果当前线程数小于核心线程数,线程池会创建新的核心线程执行任务
- 如果当前线程数达到或超过核心线程数,任务会被放入工作队列等待执行
- 如果工作队列已满,且当前线程数小于最大线程数,线程池会创建新的非核心线程执行任务
- 如果工作队列已满,且当前线程数达到最大线程数,新任务会被拒绝执行,根据拒绝策略处理
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值中:
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的核心方法,负责处理任务提交:
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接口:
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方法是工作线程执行的核心循环:
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机制:
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)。
微信
支付宝