一、协程的基本信息
二、协程的启动过程与Job生命周期
launch为什么无法将结果返回给调用方呢?
源代码如下:
public
fun
CoroutineScope.launch(
context:
CoroutineContext
=
EmptyCoroutineContext,
start:
CoroutineStart
=
CoroutineStart.DEFAULT,
block:
suspend
CoroutineScope.()
->
Unit
):
Job
{
...
}
public
fun
CoroutineScope.launch(
context:
CoroutineContext
=
EmptyCoroutineContext,
start:
CoroutineStart
=
CoroutineStart.DEFAULT,
block:
suspend
CoroutineScope.()
->
Unit
//
不同点1
):
Job
{}
//
不同点2
public
fun
<T>
CoroutineScope.async(
context:
CoroutineContext
=
EmptyCoroutineContext,
start:
CoroutineStart
=
CoroutineStart.DEFAULT,
block:
suspend
CoroutineScope.()
->
T
//
不同点1
):
Deferred<T>
{}
//
不同点2
isActive
:判断协程是否处于活跃状态isCompleted
:判断协程是否已完成(正常结束或异常结束)isCancelled
:判断协程是否已被取消
操作协程状态:
可以通过job.cancel()
方法取消协程,也可以使用withTimeout
等快捷函数设置超时。
当一个协程在另一个协程的作用域内启动时,它们会建立父子关系。父协程会等待所有子协程完成后才会结束。
import kotlinx.coroutines.*
fun main() = runBlocking {
val job = launch {
try {
println("协程启动,开始执行长时间任务")
repeat(1000) { i ->
println("正在执行任务 $i")
delay(500)
}
} finally {
// 即使协程被取消,finally块中的代码也会执行
println("协程被取消,执行清理工作")
}
}
delay(1600) // 等待一段时间后取消协程
println("准备取消协程")
job.cancel()
println("已调用取消方法,Job状态: active=${job.isActive}, completed=${job.isCompleted}, cancelled=${job.isCancelled}")
job.join() // 等待协程彻底完成(包括finally块)
println("协程最终状态: active=${job.isActive}, completed=${job.isCompleted}, cancelled=${job.isCancelled}")
}
import kotlinx.coroutines.*
fun main() = runBlocking {
val parentJob = launch {
println("父协程启动")
val childJob1 = launch {
println("子协程1启动")
delay(2000)
println("子协程1完成")
}
val childJob2 = launch {
println("子协程2启动")
delay(1000)
println("子协程2完成")
}
println("父协程继续执行其他任务")
delay(500)
println("父协程即将结束")
}
println("主线程等待父协程完成")
parentJob.join()
println("父协程完成,主线程继续执行")
}
结构化并发的本质是:协程的生命周期与创建它的作用域绑定。这种绑定通过 Job 的父子关系实现,主要规则如下:
父子关系的建立:
- 在一个协程作用域内启动的子协程,会自动成为该作用域协程的子 Job。
- 父 Job 会等待所有子 Job 完成后才能结束。
- 父 Job 被取消时,所有子 Job 会被递归取消。
生命周期管理:
- 完成规则:父 Job 只有在所有子 Job 成功完成后才算完成。
- 取消规则:父 Job 被取消或异常结束时,所有子 Job 会被立即取消。
异常传播:
- 默认情况下,子 Job 的异常会导致父 Job 取消,进而取消所有其他子 Job(类似
coroutineScope
的行为)。
- 默认情况下,子 Job 的异常会导致父 Job 取消,进而取消所有其他子 Job(类似
并行任务:同时加载多个数据源
suspend fun loadUserData(): UserData = coroutineScope {
// 并行启动两个异步任务
val userDeferred = async { apiService.getUser() }
val settingsDeferred = async { apiService.getSettings() }
// 等待所有任务完成并组合结果
UserData(
user = userDeferred.await(),
settings = settingsDeferred.await()
)
}
独立任务:互不影响的并行操作
lifecycleScope.launch {
supervisorScope {
// 这些任务独立运行,一个失败不会影响其他
launch { loadImage() }
launch { loadText() }
launch { loadComments() }
}
}
5.Job的继承(Deferred)
在 Kotlin 协程中,Deferred<T>
是 Job
的子接口,代表一个异步计算的结果,而 await()
方法则用于获取这个结果。
await()
是 Deferred<T>
的核心方法,主要功能:
- 挂起协程:调用
await()
会挂起当前协程,直到Deferred
完成(成功或失败)。 - 返回结果:如果
Deferred
成功完成,返回计算结果T
。 - 传播异常:如果
Deferred
因异常取消,await()
会抛出相同的异常。
await()
的工作原理
await()
的核心逻辑可以简化为以下步骤:
检查状态:
- 如果
Deferred
已完成(成功或失败),直接返回结果或抛出异常。 - 如果
Deferred
未完成,挂起当前协程。
- 如果
注册回调:
- 将当前协程的续体(Continuation)注册到
Deferred
的完成回调中。
- 将当前协程的续体(Continuation)注册到
恢复执行:
- 当
Deferred
完成时(无论是正常结束还是异常取消),触发回调,恢复挂起的协程。 - 如果成功,将结果传递给续体;如果失败,抛出异常。
- 当
6.SupervisorJob
public
fun
SupervisorJob(parent:
Job?
=
null)
:
CompletableJob
=
SupervisorJobImpl(parent)
public
interface
CompletableJob
:
Job
{
public
fun
complete():
Boolean
public
fun
completeExceptionally(exception:
Throwable):
Boolean
}
三、协程的挂起和恢复
1.协程怎么避免回调地狱
Java的回调地狱
getUserInfo(new
CallBack()
{
@Override
public
void
onSuccess(String
user)
{
if
(user
!=
null)
{
System.out.println(user);
getFriendList(user,
new
CallBack()
{
@Override
public
void
onSuccess(String
friendList)
{
if
(friendList
!=
null)
{
System.out.println(friendList);
getFeedList(friendList,
new
CallBack()
{
@Override
public
void
onSuccess(String
feed)
{
if
(feed
!=
null)
{
System.out.println(feed);
}
}
});
}
}
});
}
}
});
//
代码段3
val
user
=
getUserInfo()
val
friendList
=
getFriendList(user)
val
feedList
=
getFeedList(friendList)
四、kotlin同步机制
Mutex(互斥锁)
核心概念作用:确保同一时刻只有一个协程访问共享资源,防止竞态条件。
与线程锁的区别:Mutex
的 lock()
是挂起函数,不会阻塞线程,仅挂起当前协程,待锁释放后恢复执行。
状态:有「锁定」和「未锁定」两种状态,同一时刻只能被一个协程持有。
2. 关键方法
lock()
:挂起协程,直到获取锁。unlock()
:释放锁,唤醒等待的协程。withLock(block)
:简化用法,自动释放锁(等价于 try-finally
)。
public
interface
Mutex
{
public
val
isLocked:
Boolean
//
注意这里
//
↓
public
suspend
fun
lock(owner:
Any?
=
null)
public
fun
unlock(owner:
Any?
=
null)
}
//
代码段9
fun
main()
=
runBlocking
{
val
mutex
=
Mutex()
var
i
=
0
val
jobs
=
mutableListOf<Job>()
repeat(10)
{
val
job
=
launch(Dispatchers.Default)
{
repeat(1000)
{
try
{
mutex.lock()
i++
i/0
//
故意制造异常
mutex.unlock()
}
catch
(e:
Exception)
{
println(e)
}
}
}
jobs.add(job)
}
jobs.joinAll()
println("i
=
$i")
}
//
程序无法退出
fun
main()
=
runBlocking
{
val
mutex
=
Mutex()
var
i
=
0
val
jobs
=
mutableListOf<Job>()
repeat(10)
{
val
job
=
launch(Dispatchers.Default)
{
repeat(1000)
{
//
变化在这里
mutex.withLock
{
i++
}
}
}
jobs.add(job)
}
jobs.joinAll()
println("i
=
$i")
}
//
withLock的定义
public
suspend
inline
fun
<T>
Mutex.withLock(owner:
Any?
=
null,
action:
()
->
T):
T
{
lock(owner)
try
{
return
action()
}
finally
{
unlock(owner)
}
}
五、基础总结
Kotlin协程是运行在线程中的轻量Task,可在多个线程间灵活切换,成千上万个协程能同时运行在一个线程中,其“非阻塞”是语言层面的,应避免使用Thread.sleep()等阻塞式行为,尽量用delay;
启动协程的方式有launch、runBlocking和async,launch是CoroutineScope的扩展函数,返回Job,无法返回结果,其参数包括协程上下文(CoroutineContext)、启动模式(CoroutineStart,如DEFAULT代表立即执行、LAZY代表懒加载执行)和挂起函数块,runBlocking启动的协程会阻塞当前线程,仅推荐用于连接线程与协程,async返回Deferred(继承自Job,多了泛型参数和await()方法),可通过await()获取结果;
Job用于监测和操控协程生命周期,有isActive、isCompleted、isCancelled等状态,可通过cancel()取消协程,协程间存在父子关系,构成结构化并发,父协程需等所有子协程完成才结束,父协程取消会导致子协程递归取消,子协程异常默认会导致父协程及其他子协程取消,而supervisorScope中的任务相互独立,一个失败不影响其他;
协程通过挂起函数和CPS转换避免回调地狱,挂起函数需在协程或其他挂起函数中调用;Kotlin的同步机制使用Mutex,其lock()是挂起函数,不会阻塞线程,withLock{}可自动释放锁,避免因异常导致锁无法释放的问题。