多线程与线程池:从 Thread 到高效并发

简介: 本文详解Android多线程核心原理与实践:从主线程阻塞风险引出线程池必要性,系统讲解Thread、Handler通信、ThreadPoolExecutor参数机制、拒绝策略、线程数设定及生命周期管理,并对比协程底层调度,强调复用、可控、安全的并发设计原则。

为什么需要多线程?

在 Android 开发中,主线程(UI 线程)负责界面渲染和用户交互。如果在主线程执行耗时操作——网络请求、数据库读写、文件 IO——界面就会卡顿,甚至触发 ANR(Application Not Responding)。

多线程的核心目的就一个:把耗时任务移出主线程,保持 UI 流畅

Thread 基础

最直接的方式是创建 Thread

Thread {
    // 耗时操作
    val data = fetchDataFromNetwork()
    runOnUiThread {
        textView.text = data
    }
}.start()

简单粗暴,但问题很多:

  • 每次 new Thread 都有创建和销毁的开销
  • 线程数量不可控,大量请求时会创建大量线程
  • 线程间通信靠 runOnUiThread / Handler,代码容易变乱

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 * 2

ThreadPoolExecutor:底层原理

Executors 工厂方法底层都是创建 ThreadPoolExecutor,理解它的参数才能真正掌控线程池:

val pool = ThreadPoolExecutor(
    2,                          // corePoolSize:核心线程数(常驻)
    4,                          // maximumPoolSize:最大线程数
    60L, TimeUnit.SECONDS,      // 非核心线程空闲存活时间
    LinkedBlockingQueue(100),   // 任务队列
    Executors.defaultThreadFactory(),
    ThreadPoolExecutor.CallerRunsPolicy()  // 拒绝策略
)

任务提交流程

提交任务
  ↓
核心线程是否已满?── 否 → 创建核心线程执行
  ↓ 是
队列是否已满?── 否 → 放入队列等待
  ↓ 是
是否达到最大线程数?── 否 → 创建非核心线程执行
  ↓ 是
执行拒绝策略

四种拒绝策略

策略 行为
AbortPolicy(默认) 抛出 RejectedExecutionException
CallerRunsPolicy 由提交任务的线程自己执行
DiscardPolicy 直接丢弃,不报错
DiscardOldestPolicy 丢弃队列最旧的任务,重新提交

Android 中的实际场景

场景一:批量下载图片

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)

配合 Kotlin Coroutines 的现代方案

现代 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 协程,都能写出高效可靠的并发代码。

相关文章
|
1天前
|
SQL 人工智能 运维
CMDB 做了二十多年,为什么还是运维的“老大难”?
本文剖析CMDB沦为“数据坟墓”的四大根因:模型僵化难适配云原生架构、手工拓扑易过期且协作低效、多源数据冲突致可信度崩塌、关系查询性能差如“查族谱”。指出唯有构建灵活建模、自动融合、图谱化查询的“数字世界模型”,再叠加懂运维场景的AI,才能实现根因诊断、风险预判与自主修复——智能运维,从此真正落地。
36 3
|
2天前
|
存储 监控 安全
云客服系统 400 电话全栈技术方案:基于阿里云架构的设计与实践
400 电话是企业客户服务的核心入口,如何将传统语音通信与现代云客服系统深度融合,是很多企业数字化转型中面临的技术挑战。本文基于阿里云技术栈,从架构设计、核心模块实现、产品选型、性能优化等维度,完整解析云客服系统集成 400 电话的全栈技术方案。
67 1
|
1天前
|
JSON 安全 开发工具
APK 瘦身与混淆:让安装包更小、更安全、更易维护
本文系统讲解APK瘦身与混淆实践:从体积分析(APK Analyzer)、资源优化(WebP/Vector/语言/ABI控制)、代码精简(依赖审查、动态模块)、R8混淆配置(精准Keep规则、mapping管理),到发布验证与CI监控,强调持续治理而非临时压缩,兼顾体积
54 0
|
人工智能 Linux API
零门槛本地部署!手把手教你用Ollama+Chatbox玩转DeepSeek大模型
本教程介绍如何在个人电脑上免费部署DeepSeek模型,无需高端显卡。通过Ollama和Chatbox两款轻量工具,用户可以在普通CPU上流畅运行大型语言模型。Ollama支持跨平台操作,提供一键式安装和模型管理;Chatbox则是多平台AI客户端,支持多种主流模型。教程涵盖Ollama和Chatbox的安装、DeepSeek模型的下载与配置,帮助你在本地轻松搭建智能助手,适用于学术研究、代码编写和日常问答等场景。
5602 24
|
缓存 编解码 Android开发
Android内存优化之图片优化
本文主要探讨Android开发中的图片优化问题,包括图片优化的重要性、OOM错误的成因及解决方法、Android支持的图片格式及其特点。同时介绍了图片储存优化的三种方式:尺寸优化、质量压缩和内存重用,并详细讲解了相关的实现方法与属性。此外,还分析了图片加载优化策略,如异步加载、缓存机制、懒加载等,并结合多级缓存流程提升性能。最后对比了几大主流图片加载框架(Universal ImageLoader、Picasso、Glide、Fresco)的特点与适用场景,重点推荐Fresco在处理大图、动图时的优异表现。这些内容为开发者提供了全面的图片优化解决方案。
513 1
|
存储
wsl的存储路径
wsl的存储路径
2682 0
|
存储 XML Java
Android 文件数据储存之内部储存 + 外部储存
简介:本文详细介绍了Android内部存储与外部存储的使用方法及核心原理。内部存储位于手机内存中,默认私有,适合存储SharedPreferences、SQLite数据库等重要数据,应用卸载后数据会被清除。外部存储包括公共文件和私有文件,支持SD卡或内部不可移除存储,需申请权限访问。文章通过代码示例展示了如何保存、读取、追加、删除文件以及将图片保存到系统相册的操作,帮助开发者理解存储机制并实现相关功能。
3003 2
|
人工智能 算法 安全
8种提升程序猿编程能力的方法+编程思维四个核心:分解、抽象、模式识别和算法
对于程序员来说,提高自己的编程能力,算是给自己定的职业发展目标之一,不过定一个成为编程大神的目标很容易,具体做起来可能就不是一件简单的事了。首先,既然决定“我要变得更好”,得先知道“更好”是什么样子的。另外,不能“想变得更好”,却没有任何具体可行的措施。
1749 2
8种提升程序猿编程能力的方法+编程思维四个核心:分解、抽象、模式识别和算法