深入浅出安卓多线程与并发
一、什么是多线程?为什么需要它?
想象你开了一家奶茶店(你的APP),如果只有一个员工(主线程):
- 既要收银(UI交互)
- 又要做奶茶(耗时操作)
- 还要打扫卫生(系统任务)
结果就是:顾客(用户)等得不耐烦走掉了(ANR应用无响应)。多线程就是雇佣更多员工(创建线程),分工合作提高效率。
安卓为什么特别需要多线程?
- 主线程(UI线程)很忙:负责所有界面更新
- 耗时操作会"卡死"界面:网络请求、数据库读写、图片处理等
- 系统规定:主线程阻塞超过5秒就会触发ANR
二、安卓多线程的四大金刚
1. Thread:基础工人
new Thread(() -> {
// 后台工作
runOnUiThread(() -> {
// 回到主线程更新UI
});
}).start();
特点:
- 最基础的线程创建方式
- 需要手动管理生命周期
- 不能直接更新UI(需要通过runOnUiThread或Handler)
2. Handler/Looper:消息快递员
Handler handler = new Handler(Looper.getMainLooper()) {
@Override
public void handleMessage(Message msg) {
// 在主线程处理消息
}
};
new Thread(() -> {
// 在子线程发送消息
handler.sendEmptyMessage(0);
}).start();
工作流程: 子线程 → 发送消息 → MessageQueue → Looper循环取出 → Handler处理
3. AsyncTask:退休的老管家(已废弃但常见于旧代码)
new AsyncTask<Void, Void, String>() {
@Override
protected String doInBackground(Void... voids) {
return "结果"; // 后台执行
}
@Override
protected void onPostExecute(String result) {
textView.setText(result); // 主线程更新UI
}
}.execute();
特点:
- 简单易用但已废弃(Android 11+)
- 容易引发内存泄漏
- 不适合长时间任务
4. 线程池:专业团队管理
ExecutorService executor = Executors.newFixedThreadPool(4);
executor.execute(() -> {
// 并发任务
});
优势:
- 避免频繁创建/销毁线程
- 控制并发数量
- 提供任务队列
三、现代安卓并发工具包(Jetpack)
1. Coroutine(协程):轻量级线程
lifecycleScope.launch {
val result = withContext(Dispatchers.IO) { // 切换到IO线程
doNetworkRequest() // 网络请求
}
textView.text = result // 自动切回主线程更新UI
}
特点:
- 用同步写法写异步代码
- 结构化并发,自动取消
- 轻量(可创建上千个"协程"而不卡顿)
2. WorkManager:定时后台工
val request = OneTimeWorkRequestBuilder<MyWorker>().build()
WorkManager.getInstance(context).enqueue(request)
适用场景:
- 延迟任务
- 定期同步数据
- 保证任务最终执行(即使APP退出)
3. LiveData:数据快递员
val liveData = MutableLiveData<String>()
liveData.observe(this) { value ->
textView.text = value // 自动在主线程更新
}
thread {
liveData.postValue("新数据") // 子线程发送
}
优势:
- 自动感知生命周期
- 保证UI更新在主线程
- 数据驱动界面
四、多线程的三大难题与解法
1. 线程安全:防止数据打架
问题场景:多个线程同时修改同一个银行账户余额
解决方案:
- 加锁:
synchronized(lock) { 临界区代码 } - 使用原子类:
AtomicInteger - 用协程的Mutex:
mutex.withLock { }
2. 内存泄漏:防止员工赖着不走
问题场景:Activity销毁了,但线程还在运行并持有Activity引用
解决方案:
- 使用ViewModel+LiveData
- 协程用lifecycleScope
- 手动取消:
thread.interrupt()
3. 线程爆炸:防止雇佣太多员工
问题场景:瞬间创建1000个线程导致OOM
解决方案:
- 使用线程池(如
Dispatchers.IO) - 限制并发数量
- 用协程替代线程
五、实际开发中的选择指南
| 场景 | 推荐方案 | 不推荐方案 |
|---|---|---|
| 简单后台任务 | 协程(IO调度器) | AsyncTask |
| 定期后台任务 | WorkManager | Timer |
| 高频率UI更新 | Handler/LiveData | 直接线程切换 |
| CPU密集型计算 | 线程池(固定大小) | 无限创建线程 |
| 网络请求+UI更新 | 协程+Retrofit | 裸Thread+Handler |
六、性能优化小贴士
-
避免在主线程做这些事:
- 网络请求
- 数据库操作
- 大图片解码
- JSON解析
-
协程最佳实践:
- 用
viewModelScope替代lifecycleScope(防止旋转屏幕重建) - IO密集型用
Dispatchers.IO - CPU密集型用
Dispatchers.Default
- 用
-
线程池配置原则:
- CPU密集型:核心数=CPU核心数
- IO密集型:核心数可以多一些(如CPU核心数*2)
七、调试多线程问题
- ANR日志:查看/data/anr/traces.txt
- Thread Dump:
adb shell kill -3 <pid> - Android Studio工具:
- Profiler中的CPU和线程视图
- 协程调试插件
八、总结
安卓多线程就像管理一家餐厅:
- 主线程是前台接待员(必须保持响应)
- 后台线程是厨房员工(处理耗时工作)
- 协程是全能型临时工(灵活高效)
- 线程安全就像避免多个厨师同时用同一个锅
记住三个黄金法则:
- 不阻塞主线程(5秒ANR红线)
- 合理控制并发量(避免线程爆炸)
- 注意生命周期管理(防止内存泄漏)
用好多线程,让你的APP像优秀餐厅一样:前台流畅响应,后台高效运作!