在 Android 开发中,主线程(UI 线程)负责界面渲染和用户交互。如果在主线程执行耗时操作——网络请求、数据库读写、文件 IO——界面就会卡顿,甚至触发 ANR(Application Not Responding)。
多线程的核心目的就一个:把耗时任务移出主线程,保持 UI 流畅。
最直接的方式是创建 Thread:
Thread {
// 耗时操作
val data = fetchDataFromNetwork()
runOnUiThread {
textView.text = data
}
}.start()简单粗暴,但问题很多:
- 每次 new Thread 都有创建和销毁的开销
- 线程数量不可控,大量请求时会创建大量线程
- 线程间通信靠 runOnUiThread / Handler,代码容易变乱
Android 提供了 Handler 机制来做线程间通信:
val handler = Handler(Looper.getMainLooper())
Thread {
val result = doHeavyWork()
handler.post {
// 回到主线程更新 UI
textView.text = result
}
}.start()Handler 解决了「子线程完成后怎么通知主线程」的问题,但并没有解决线程管理的问题。
线程池(ExecutorService)是 Java 提供的线程管理方案,核心思想:复用线程,控制并发数量。
// 固定大小线程池 —— 最常用的选择
val fixedPool = Executors.newFixedThreadPool(4)
// 缓存线程池 —— 适合大量短时任务
val cachedPool = Executors.newCachedThreadPool()
// 单线程池 —— 保证任务顺序执行
val singlePool = Executors.newSingleThreadExecutor()
// 定时线程池 —— 周期性任务
val scheduledPool = Executors.newScheduledThreadPool(2)val executor = Executors.newFixedThreadPool(4)
// 提交任务
executor.execute {
val bitmap = downloadImage(url)
handler.post { imageView.setImageBitmap(bitmap) }
}
// 提交有返回值的任务
val future: Future<String> = executor.submit(Callable {
fetchDataFromServer()
})
// 获取结果(会阻塞当前线程)
val result = future.get()// CPU 密集型:线程数 ≈ CPU 核心数
val cpuThreads = Runtime.getRuntime().availableProcessors()
// IO 密集型:线程数可以多一些(网络、文件操作等待时间长)
val ioThreads = cpuThreads * 2Executors 工厂方法底层都是创建 ThreadPoolExecutor,理解它的参数才能真正掌控线程池:
val pool = ThreadPoolExecutor(
2, // corePoolSize:核心线程数(常驻)
4, // maximumPoolSize:最大线程数
60L, TimeUnit.SECONDS, // 非核心线程空闲存活时间
LinkedBlockingQueue(100), // 任务队列
Executors.defaultThreadFactory(),
ThreadPoolExecutor.CallerRunsPolicy() // 拒绝策略
)提交任务
↓
核心线程是否已满?── 否 → 创建核心线程执行
↓ 是
队列是否已满?── 否 → 放入队列等待
↓ 是
是否达到最大线程数?── 否 → 创建非核心线程执行
↓ 是
执行拒绝策略策略 | 行为 |
|---|---|
`AbortPolicy`(默认) | 抛出 `RejectedExecutionException` |
`CallerRunsPolicy` | 由提交任务的线程自己执行 |
`DiscardPolicy` | 直接丢弃,不报错 |
`DiscardOldestPolicy` | 丢弃队列最旧的任务,重新提交 |
val executor = Executors.newFixedThreadPool(4)
fun loadImages(urls: List<String>, callback: (String, Bitmap) -> Unit) {
val handler = Handler(Looper.getMainLooper())
urls.forEach { url ->
executor.execute {
val bitmap = downloadBitmap(url)
handler.post { callback(url, bitmap) }
}
}
}// 单线程池保证写入顺序
val dbExecutor = Executors.newSingleThreadExecutor()
fun saveUser(user: User) {
dbExecutor.execute {
database.userDao().insert(user)
}
}
fun saveLog(log: LogEntry) {
dbExecutor.execute {
database.logDao().insert(log)
}
}val scheduler = Executors.newScheduledThreadPool(1)
// 每小时清理一次过期缓存
scheduler.scheduleAtFixedRate({
clearExpiredCache()
}, 0, 1, TimeUnit.HOURS)现代 Android 开发推荐用协程替代手动线程管理,但理解线程池原理依然重要:
// 协程内部依然可以使用线程池
val ioDispatcher = Executors.newFixedThreadPool(4).asCoroutineDispatcher()
viewModelScope.launch {
val result = withContext(ioDispatcher) {
fetchData()
}
updateUI(result)
}协程的 Dispatchers.IO 底层就是一个共享线程池(默认 64 个线程上限)。理解线程池,才能更好地理解协程的调度行为。
多线程带来的不只是性能问题,还有数据一致性:
var counter = 0
// 多线程同时执行 counter++,结果可能不对
fun increment() {
counter++ // 非原子操作!
}// 方案一:synchronized
@Synchronized
fun increment() {
counter++
}
// 方案二:原子变量
val counter = AtomicInteger(0)
fun increment() {
counter.incrementAndGet()
}
// 方案三:volatile(保证可见性,不保证原子性)
@Volatile
var flag = false线程池不关掉,线程会一直存活,可能导致内存泄漏:
class MyActivity : AppCompatActivity() {
private val executor = Executors.newFixedThreadPool(4)
override fun onDestroy() {
super.onDestroy()
// 停止接收新任务,等待已提交任务完成
executor.shutdown()
// 或者立即中断所有线程
// executor.shutdownNow()
}
}更好的做法是把线程池交给 ViewModel 管理:
class MyViewModel : ViewModel() {
private val executor = Executors.newFixedThreadPool(4)
override fun onCleared() {
super.onCleared()
executor.shutdown()
}
fun loadData() {
executor.execute {
val data = repository.fetch()
_result.postValue(data)
}
}
}方案 | 适用场景 | 注意点 |
|---|---|---|
`Thread` | 简单一次性任务 | 用完即毁,别滥用 |
`Handler` | 线程间通信 | 注意内存泄漏 |
`FixedThreadPool` | 并发执行、控制资源 | 合理设置线程数 |
`SingleThreadExecutor` | 保证顺序执行 | 队列会堆积 |
`ScheduledThreadPool` | 定时/周期任务 | 记得关闭 |
Kotlin Coroutines | 现代异步方案 | 底层仍是线程池 |
多线程不是银弹,核心原则:用最少的线程完成最多的工作,同时保证数据安全。线程池就是这一原则的工程化实现——复用线程、控制并发、管理队列。理解了这些,不管是用传统 Java 线程池还是 Kotlin 协程,都能写出高效可靠的并发代码。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。