一、CoroutineContext:行为像 Map 的链表结构
类似 Map 的特性
CoroutineContext 表现为不可变映射集合,可通过 Key 获取对应 Element(如 coroutineContext[Job])。同一上下文中每个 Key 只能对应一个 Element。
底层实现为链表
与 Java/Kotlin 的 HashMap 不同,CoroutineContext 的核心实现类 CombinedContext 是单向链表:
- 使用
+运算符合并上下文时(如context1 + context2),系统会创建新的CombinedContext节点 - 新节点的
left指向原上下文,element指向新加入的元素 - 该设计使上下文组合高效(无需复制整个集合),但查找元素时需遍历链表
二、Key 的单例引用机制
Key 的定义
每个 CoroutineContext.Element 都有 key 属性,类型为 CoroutineContext.Key<E>。
引用比较(Reference Equality)
获取元素时,Kotlin 协程库使用引用相等性 (===)而非值相等性(==)来比较 Key。这意味着从上下文中取出元素时,需传入与该元素内部存储完全相同的 Key 对象实例。
因此标准 Key(如 Job.Key、CoroutineName.Key、ContinuationInterceptor.Key)通常定义为单例对象 (object)或伴生对象中的静态单例。
注意:若使用实例不同但内容相同的 Key 对象查找,将无法找到对应元素。
三、上下文组合与覆盖规则
+ 运算符
contextA + contextB 时,若 contextB 包含相同 Key 的元素,则会覆盖 contextA 中的对应元素。
查找顺序
查找从链表右侧(最新元素)向左遍历,找到匹配 Key 即返回,保证后加入覆盖先加入的语义。
四、父子协程上下文的独立性
每当启动新协程时,系统都会创建独立的 CoroutineContext 实例。由于底层基于 CombinedContext 单向链表实现,父协程与子协程在内存中维护着两条独立链表。
五、Job 父子关系的查询
Job 通过 parent 与 children 属性维护树状结构,支撑结构化并发。利用这两个引用可管理协程生命周期、取消传播与异常处理。
5.1 查找父 Job
kotlin
val parentJob = Job()
val scope = CoroutineScope(parentJob)
scope.launch {
val currentJob = this.coroutineContext[Job]
// 查找父 Job
val foundParent = currentJob?.parent
println("当前 Job: $currentJob")
println("父 Job: $foundParent")
println("是否是同一个父 Job? ${foundParent == parentJob}") // 输出: true
}
5.2 查找子 Job
scss
val parentJob = Job()
val scope = CoroutineScope(parentJob)
// 启动两个子协程
val child1 = scope.launch { delay(1000) }
val child2 = scope.launch { delay(2000) }
// 查找子 Jobs
val children = parentJob.children.toList()
println("子协程数量: ${children.size}") // 输出: 2
println("包含 child1? ${children.contains(child1)}") // 输出: true
println("包含 child2? ${children.contains(child2)}") // 输出: true
// 遍历子协程
parentJob.children.forEach { childJob ->
println("子 Job: $childJob, isActive: ${childJob.isActive}")
}
六、SupervisorJob 的异常隔离机制
6.1 子 Job 的通知机制
当子协程因异常或取消而结束时,会调用 JobSupport 中的 cancelParent 方法。该方法通过 parentHandle(即父 Job)向上发送通知。
kotlin
private fun cancelParent(cause: Throwable): Boolean {
// 1. 判断是否是正常的取消异常
val isCancellation = cause is CancellationException
// 2. 获取父协程的句柄 (即 SupervisorJob 或普通 Job)
val parent = parentHandle
// 如果没有父节点,直接返回
if (parent === null || parent === NonDisposableHandle) {
return isCancellation
}
// 3. 【关键步骤】调用父节点的 childCancelled 方法
// 如果父节点返回 true,或者原因是 CancellationException,则当前协程认为父节点已处理(或需要传播取消)
return parent.childCancelled(cause) || isCancellation
}
6.2 SupervisorJob 的响应策略
kotlin
internal class SupervisorJobImpl(parent: Job?) : JobImpl(parent) {
override fun childCancelled(cause: Throwable): Boolean {
// 1. 如果是 CancellationException,交给父类处理(通常返回 true,允许正常取消传播)
if (cause is CancellationException) {
return super.childCancelled(cause)
}
// 2. 【关键区别】对于其他异常(如 RuntimeException),直接返回 false
// 这意味着:"我收到了子协程的失败通知,但我决定不取消我自己,也不向我的父节点传播这个错误"
return false
}
}
核心差异 :普通 Job 遇到子协程异常会向上传播取消,而
SupervisorJob对非CancellationException异常返回false,实现异常隔离。
6.3 取消传播的底层实现
kotlin
private fun notifyCancelling(list: NodeList, cause: Throwable) {
// 1. 通知所有监听器
notifyHandlers<JobCancellingNode<*>>(list, cause)
// 2. 通知父节点(如果是根节点则忽略)
cancelParent(cause)
}