Android-重学kotlin(协程基础)新学习总结
一、协程的基本信息
二、协程的启动过程与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():
Booleanpublic
fun
completeExceptionally(exception:
Throwable):
Boolean
}
三、协程的挂起和恢复
1.协程怎么避免回调地狱
Java的回调地狱
getUserInfo(new
CallBack()
{@Overridepublic
void
onSuccess(String
user)
{if
(user
!=
null)
{System.out.println(user);getFriendList(user,
new
CallBack()
{@Overridepublic
void
onSuccess(String
friendList)
{if
(friendList
!=
null)
{System.out.println(friendList);getFeedList(friendList,
new
CallBack()
{@Overridepublic
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
=
0val
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
=
0val
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{}可自动释放锁,避免因异常导致锁无法释放的问题。