Kotlin 协程从入门到专家:完全教程覆盖基础实践、进阶技巧与架构设计
在移动开发、后端服务等领域,异步编程是绕不开的核心课题。Java的Thread、Future模式存在代码冗余、回调地狱等问题,而Kotlin协程(Coroutine)作为一种轻量级并发方案,以"轻量、高效、简洁"的特性彻底改变了异步编程的体验。它既保留了同步代码的可读性,又具备异步执行的高效性,已成为Kotlin开发的必备技能。本文从入门到专家,通过大量实战代码,全面解析协程的基础原理、实践技巧与架构设计思路,助力开发者彻底掌握这一核心技术。
一、协程基础:从"是什么"到"第一次运行"
在深入实践前,我们首先要明确协程的核心定位:协程是一种可暂停的计算过程,它能在不阻塞线程的情况下暂停执行,释放线程资源用于其他任务。与线程相比,协程的创建成本极低(占用内存仅几KB),一个线程可承载数千个协程,大幅提升系统并发能力。
1. 环境准备:引入协程依赖
无论是JVM后端、Android还是桌面应用,使用协程前需先引入官方依赖。以Gradle项目为例,在build.gradle.kts中添加:
scss
// 核心依赖(必选)
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.3")
// JVM平台专用(如后端、桌面应用)
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-jdk8:1.7.3")
// Android平台专用
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.3")
2. 第一个协程程序:launch与runBlocking
协程不能独立运行,必须依托于协程作用域(CoroutineScope) 。作用域用于管理协程的生命周期,确保资源可控。最基础的启动协程方式是使用launch
函数,它会创建一个新的协程并异步执行:
kotlin
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
fun main() = runBlocking {
// runBlocking创建一个阻塞当前线程的协程作用域,用于桥接同步代码与协程
println("主线程开始:${Thread.currentThread().name}")
// launch创建子协程,在作用域内异步执行
launch {
println("协程执行:${Thread.currentThread().name}")
Thread.sleep(1000) // 模拟耗时操作(实际开发用delay)
println("协程完成:${Thread.currentThread().name}")
}
Thread.sleep(2000) // 等待协程执行完成(实际开发不用此方式)
println("主线程结束:${Thread.currentThread().name}")
}
运行结果:
css
主线程开始:main
协程执行:main
协程完成:main
主线程结束:main
关键说明:runBlocking
主要用于测试和main函数,会阻塞当前线程直到作用域内所有协程执行完成;launch
创建的是"火与忘"(fire-and-forget)协程,不返回结果,若需获取执行结果需使用async/await
。
3. 核心关键字:suspend与delay
上述代码中Thread.sleep(1000)
会阻塞线程,违背协程"非阻塞"的核心优势。协程中应使用delay
函数实现暂停,它是一个挂起函数(suspend function) ------即能暂停协程执行并释放线程的函数。
挂起函数必须在协程作用域或其他挂起函数中调用,通过suspend
关键字定义。修改上述代码为非阻塞版本:
kotlin
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
// 自定义挂起函数:模拟耗时操作
suspend fun doHeavyWork() {
println("耗时操作开始:${Thread.currentThread().name}")
delay(1000) // 非阻塞暂停,释放线程执行其他任务
println("耗时操作结束:${Thread.currentThread().name}")
}
fun main() = runBlocking {
println("主线程开始:${Thread.currentThread().name}")
launch {
doHeavyWork() // 挂起函数只能在协程作用域内调用
}
println("协程启动后,主线程继续执行:${Thread.currentThread().name}")
delay(2000) // 非阻塞等待
println("主线程结束:${Thread.currentThread().name}")
}
运行结果:
css
主线程开始:main
协程启动后,主线程继续执行:main
耗时操作开始:main
耗时操作结束:main
主线程结束:main
可见,delay
暂停协程时,线程并未被阻塞,而是继续执行了"协程启动后..."的打印逻辑,充分体现了协程的非阻塞特性。
二、基础实践:协程的启动、取消与结果获取
掌握基础概念后,我们需要深入实践协程的核心操作:不同方式启动协程、灵活取消协程生命周期、安全获取执行结果。
1. 协程启动方式:launch、async与runBlocking
根据场景需求,协程有三种核心启动方式,其差异主要体现在"是否阻塞线程"和"是否返回结果":
- launch :异步启动,不返回结果,返回
Job
对象用于管理协程生命周期(取消、等待)。 - async :异步启动,返回
Deferred
对象(继承自Job),通过await()
方法获取执行结果。 - runBlocking:同步启动,阻塞当前线程,用于桥接同步与异步代码,不建议在生产环境使用。
实战代码:三种启动方式对比
kotlin
import kotlinx.coroutines.async
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
fun main() = runBlocking {
// 1. launch:无返回值,通过Job管理
val job = launch {
delay(1000)
println("launch协程完成")
}
job.join() // 等待协程完成(非阻塞)
println("launch协程已结束")
// 2. async:有返回值,通过Deferred.await()获取
val deferred = async {
delay(1000)
println("async协程计算完成")
100 + 200 // 返回计算结果
}
val result = deferred.await() // 等待并获取结果(非阻塞)
println("async协程返回结果:$result")
// 3. 并行执行多个async协程(高效并发)
val deferred1 = async { delay(1000); 100 }
val deferred2 = async { delay(1500); 200 }
val total = deferred1.await() + deferred2.await()
println("并行计算结果:$total,耗时约1500ms(取最长任务时间)")
}
运行结果:
csharp
launch协程完成
launch协程已结束
async协程计算完成
async协程返回结果:300
并行计算结果:300,耗时约1500ms(取最长任务时间)
关键优势:多个async
协程并行执行时,总耗时取决于最长任务,而非任务耗时之和,大幅提升并发效率。
2. 协程取消:Job与生命周期管理
协程的取消是通过Job
对象实现的,调用job.cancel()
可请求协程取消,job.isCancelled
可判断协程是否已取消。但需注意:协程取消是协作式的,只有当协程执行到挂起函数时才会响应取消请求。
实战代码:协程取消与协作
scss
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withTimeout
fun main() = runBlocking {
// 1. 基础取消方式
val job = launch {
repeat(10) { i ->
println("协程执行中:第${i+1}次")
delay(500) // 挂起函数,响应取消
}
}
delay(1200) // 等待1.2秒后取消
job.cancel()
job.join() // 等待协程真正结束
println("基础取消完成")
// 2. 超时取消(常用场景)
try {
withTimeout(1000) { // 1秒超时后自动取消协程
repeat(5) { i ->
println("超时检测中:第${i+1}次")
delay(400)
}
}
} catch (e: TimeoutCancellationException) {
println("协程超时被取消")
}
// 3. 不可取消的协程(withContext(NonCancellable))
val nonCancelJob = launch {
withContext(NonCancellable) {
repeat(3) { i ->
println("不可取消执行中:第${i+1}次")
delay(500)
}
}
println("不可取消部分结束后,协程才会取消")
}
delay(800)
nonCancelJob.cancel()
nonCancelJob.join()
println("不可取消协程处理完成")
}
运行结果:
协程执行中:第1次
协程执行中:第2次
协程执行中:第3次
基础取消完成
超时检测中:第1次
超时检测中:第2次
超时检测中:第3次
协程超时被取消
不可取消执行中:第1次
不可取消执行中:第2次
不可取消执行中:第3次
不可取消部分结束后,协程才会取消
不可取消协程处理完成
3. 异常处理:try-catch与CoroutineExceptionHandler
协程的异常处理分为两种场景:launch
创建的协程异常会直接传播到作用域,async
创建的协程异常会在调用await()
时抛出。可通过try-catch
捕获单个协程异常,或通过CoroutineExceptionHandler
统一处理作用域内所有协程异常。
kotlin
import kotlinx.coroutines.CoroutineExceptionHandler
import kotlinx.coroutines.async
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
fun main() = runBlocking {
// 1. 单个协程异常:try-catch捕获
val deferred = async {
throw IllegalArgumentException("async协程执行异常")
100 // 不会执行
}
try {
deferred.await()
} catch (e: IllegalArgumentException) {
println("捕获async异常:${e.message}")
}
// 2. 多个协程统一异常处理:CoroutineExceptionHandler
val exceptionHandler = CoroutineExceptionHandler { _, throwable ->
println("统一异常处理:${throwable.message}")
}
// 给作用域添加异常处理器
val scope = this + exceptionHandler
scope.launch {
throw IllegalStateException("launch协程1异常")
}
scope.launch {
throw NullPointerException("launch协程2异常")
}
delay(100) // 等待异常抛出
println("统一异常处理完成")
}
运行结果:
csharp
捕获async异常:async协程执行异常
统一异常处理:launch协程1异常
统一异常处理完成
注意:CoroutineExceptionHandler
仅能处理launch
协程的异常,async
协程的异常需通过await()
结合try-catch
捕获。
三、进阶技巧:调度器、作用域与结构化并发
要真正灵活运用协程,必须掌握调度器(线程分配)、自定义作用域(生命周期管理)和结构化并发(资源安全)三大核心进阶技巧。
1. 协程调度器:控制线程分配
协程调度器(CoroutineDispatcher
)决定了协程在哪个线程或线程池中执行,核心调度器类型及适用场景如下:
- Dispatchers.Default:默认调度器,使用共享线程池,适用于CPU密集型任务(如计算、排序)。
- Dispatchers.IO:IO调度器,使用共享线程池,适用于IO密集型任务(如网络请求、数据库操作、文件读写)。
- Dispatchers.Main:主线程调度器,仅在Android、桌面应用等有主线程的环境中可用,适用于更新UI。
- Dispatchers.Unconfined:无约束调度器,协程启动时在当前线程执行,暂停后恢复到挂起函数所在的线程,慎用。
- newSingleThreadContext(name) :创建单线程调度器,适用于需要串行执行的任务,使用后需手动关闭。
实战代码:调度器使用场景
scss
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withContext
fun main() = runBlocking {
// 1. IO调度器:模拟网络请求
launch(Dispatchers.IO) {
println("IO任务开始:${Thread.currentThread().name}")
delay(1000) // 模拟网络请求
println("IO任务结束:${Thread.currentThread().name}")
// 切换到主线程(若有主线程环境,如Android)
// withContext(Dispatchers.Main) { updateUI() }
}
// 2. Default调度器:模拟CPU密集计算
launch(Dispatchers.Default) {
println("CPU计算开始:${Thread.currentThread().name}")
val result = (1..1000000).sum() // 密集计算
println("CPU计算结果:$result,线程:${Thread.currentThread().name}")
}
// 3. 自定义单线程调度器
val singleDispatcher = newSingleThreadContext("CustomThread")
launch(singleDispatcher) {
repeat(3) {
println("自定义线程执行:${Thread.currentThread().name},第${it+1}次")
delay(500)
}
}
delay(2000)
singleDispatcher.close() // 手动关闭自定义调度器
println("调度器演示完成")
}
运行结果:
IO任务开始:DefaultDispatcher-worker-1
CPU计算开始:DefaultDispatcher-worker-2
自定义线程执行:CustomThread,第1次
CPU计算结果:500000500000,线程:DefaultDispatcher-worker-2
IO任务结束:DefaultDispatcher-worker-1
自定义线程执行:CustomThread,第2次
自定义线程执行:CustomThread,第3次
调度器演示完成
2. 自定义协程作用域:结构化并发核心
生产环境中,我们很少直接使用runBlocking
,而是通过自定义协程作用域管理协程生命周期,实现"结构化并发"------即作用域内的协程会随着作用域的销毁而自动取消,避免内存泄漏。
自定义作用域的核心是实现CoroutineScope
接口,该接口仅需一个coroutineContext
属性(由调度器+异常处理器+Job组成)。
实战代码:AndroidViewModel场景的自定义作用域(后端场景类似)
kotlin
import androidx.lifecycle.ViewModel
import kotlinx.coroutines.CoroutineExceptionHandler
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.launch
import kotlinx.coroutines.delay
// 自定义协程作用域,绑定ViewModel生命周期
class MyViewModel : ViewModel(), CoroutineScope {
// 1. 定义Job:管理协程生命周期
private val viewModelJob = Job()
// 2. 定义异常处理器
private val exceptionHandler = CoroutineExceptionHandler { _, throwable ->
println("ViewModel异常:${throwable.message}")
}
// 3. 实现coroutineContext:调度器+Job+异常处理器
override val coroutineContext = Dispatchers.Main + viewModelJob + exceptionHandler
// 4. 业务方法:启动协程
fun fetchData() {
launch(Dispatchers.IO) { // 切换到IO线程执行网络请求
println("请求数据:${Thread.currentThread().name}")
delay(1500) // 模拟网络请求
val data = "获取到的服务器数据"
// 切换回主线程更新UI
withContext(Dispatchers.Main) {
updateUI(data)
}
}
}
private fun updateUI(data: String) {
println("更新UI:$data,线程:${Thread.currentThread().name}")
}
// 5. 生命周期销毁时取消所有协程
override fun onCleared() {
super.onCleared()
viewModelJob.cancel() // 取消作用域内所有协程
println("ViewModel销毁,协程已取消")
}
}
// 测试代码
fun main() = runBlocking {
val viewModel = MyViewModel()
viewModel.fetchData()
delay(1000) // 模拟ViewModel存活1秒
viewModel.onCleared() // 模拟ViewModel销毁
delay(1000) // 验证协程是否已取消
println("测试完成")
}
运行结果:
请求数据:DefaultDispatcher-worker-1
ViewModel销毁,协程已取消
测试完成
关键价值:当ViewModel销毁时,调用viewModelJob.cancel()
可一次性取消作用域内所有协程,避免因协程继续执行导致的内存泄漏或空指针异常。
四、架构设计:协程在实际项目中的最佳实践
在大型项目中,协程的使用需结合架构分层(如MVVM、Clean Architecture),形成标准化的异步处理方案。以下是后端服务和Android应用两大场景的最佳实践。
1. 后端服务场景:协程与Spring Boot集成
Kotlin协程可与Spring Boot无缝集成,通过@CoroutineScope
注解或自定义作用域,实现非阻塞的HTTP接口和数据库操作,大幅提升服务并发能力。
实战代码:Spring Boot + 协程实现REST接口
kotlin
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.withContext
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.PathVariable
import org.springframework.web.bind.annotation.RestController
// 1. 启用协程支持(Spring Boot 2.3+ 内置支持)
// @SpringBootApplication
// class CoroutineBackendApplication
@RestController
class UserController(
private val userService: UserService
) {
// 2. 协程接口:使用suspend关键字定义
@GetMapping("/users/{id}")
suspend fun getUser(@PathVariable id: Long): UserDTO {
// 自动使用Spring的协程调度器,无需手动管理作用域
return userService.getUserById(id)
}
@GetMapping("/users/batch")
suspend fun getBatchUsers(@PathVariable ids: List
核心优势:协程接口的并发能力远超传统同步接口,单个Tomcat线程可处理数千个并发请求,且代码比基于CompletableFuture的异步接口更简洁。
2. Android应用场景:MVVM + 协程 + Flow架构
Android开发中,协程常与Jetpack组件(如ViewModel、Flow、Room)结合,形成"ViewModel-Repository-DataSource"的分层架构,实现数据获取与UI更新的非阻塞处理。
架构流程图(核心链路):
暂时无法在豆包文档外展示此内容
实战代码:核心层实现
kotlin
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.withContext
// 1. 数据模型
data class User(val id: Long, val name: String)
data class UserUiState(val isLoading: Boolean, val data: User?, val error: String?)
// 2. 数据源层:RemoteDataSource
interface RemoteDataSource {
suspend fun fetchUserById(id: Long): User
}
class RemoteDataSourceImpl(private val apiService: ApiService) : RemoteDataSource {
override suspend fun fetchUserById(id: Long): User {
// Retrofit支持suspend方法,自动在IO调度器执行
return apiService.getUser(id)
}
}
// Retrofit API接口
interface ApiService {
@GET("users/{id}")
suspend fun getUser(@Path("id") id: Long): User
}
// 3. 仓库层:Repository(数据融合)
class UserRepository(
private val remoteDataSource: RemoteDataSource,
private val localDataSource: LocalDataSource
) {
// 流方式:实时返回数据状态(加载中、成功、失败)
fun getUserFlow(id: Long): Flow
核心优势:通过协程+Flow实现"数据获取-状态管理-UI更新"的全链路非阻塞,代码简洁且可维护性高;仓库层的本地+远程数据融合,提升应用离线可用性。
五、总结与进阶方向
Kotlin协程并非替代线程,而是构建在线程之上的轻量级并发框架,其核心价值在于"用同步代码的可读性实现异步执行的高效性"。从基础的launch/async
到进阶的调度器、自定义作用域,再到架构层面的最佳实践,协程的学习需遵循"原理-实践-架构"的递进路径。
进阶学习方向:
- 协程底层原理:深入理解挂起函数的CPS转换、协程上下文切换机制。
- 高级API运用 :如
channel
(协程间通信)、select
(多协程等待)、flow
(响应式数据流)。 - 性能优化:调度器线程池配置、协程并发数控制、避免过度挂起。
- 跨平台运用:在Kotlin Multiplatform(KMP)项目中使用协程实现跨平台异步逻辑。
掌握协程不仅能提升异步编程效率,更能培养"结构化并发"的思维,为构建高可用、高性能的Kotlin应用奠定坚实基础。