Kotlin协程,直面秋招,高级android面试题

delay(1000)
“job1-finish”
}
val job2 = async(Dispatchers.IO) {
delay(2000)
“job2-finish”
}
val job3 = async(Dispatchers.IO) {
delay(500)
“job3-finish”
}
//等待各job执行完 将结果合并
Alerter.create(this@LearnCoroutineActivity).setTitle(“Result”)
.setText(“job1: j o b 1. a w a i t ( ) , j o b 2 : {job1.await()},job2: job1.await(),job2:{job2.await()},job3:${job3.await()}”).show()
}
}

可以看到,这就是同步代码风格编写异步代码的强大之处,下面开始源码解析

3、coroutineContext解析

  • CoroutineContext是一个特殊的集合,同时包含了Map和Set的特点

  • 集合内部的元素Element是根据key去对应(Map特点),但是不允许重复(Set特点)

  • Element之间可以通过+号进行组合

  • Element有如下四类,共同组成了CoroutineContext

  • Job:协程的唯一标识,用来控制协程的生命周期(new、active、completing、completed、cancelling、cancelled)

  • CoroutineDispatcher:指定协程运行的线程(IO、Default、Main、Unconfined)

  • CoroutineName: 指定协程的名称,默认为coroutine

  • CoroutineExceptionHandler: 指定协程的异常处理器,用来处理未捕获的异常

3.1、Job Element

  • 每一个所创建的协程 (通过 launch 或者 async),会返回一个 Job实例,该实例是协程的唯一标识,并且负责管理协程的生命周期
Job状态

Job在执行的过程中,包含了一系列状态,虽然开发者没办法直接获取所有状态,但是Job之中有如下三个属性

  • isActive(是否活动)
  • isCompleted(是否已完成)
  • isCancelled(是否已取消)

根据属性就可以推断出Job的所处状态,状态如下

  • 新创建 (New)
  • 当一个协程创建后就处于新建(New)状态
  • 活跃 (Active)
  • 当调用Job的start/join方法后协程就处于活跃(Active)状态
  • 完成中 (Completing)
  • 当协程执行完成后或者调用CompletableJob(CompletableJob是Job的一个子接口)的complete方法都会让当前协程进入完成中(Completing)状态
  • 已完成 (Completed)
  • 处于完成中状态的协程会等所有子协程都完成后才进入完成(Completed)状态
  • 取消中 (Cancelling)
  • 当运行出错或者调用Job的cancel方法都会将当前协程置为取消中(Cancelling)状态
  • 已取消 (Cancelled)
  • 处于取消中状态的协程会等所有子协程都完成后才进入取消 (Cancelled)状态
StateisActiveisCompletedisCancelled
New (optional initial state)falsefalsefalse
Active (default initial state)truefalsefalse
Completing (transient state)truefalsefalse
Cancelling (transient state)falsefalsetrue
Cancelled (final state)falsetruetrue
Completed (final state)falsetruefalse

wait children
±----+ start ±-------+ complete ±------------+ finish ±----------+
| New | -----> | Active | ---------> | Completing | -------> | Completed |
±----+ ±-------+ ±------------+ ±----------+
| cancel / fail |
| ±---------------+
| |
V V
±-----------+ finish ±----------+
| Cancelling | --------------------------------> | Cancelled |
±-----------+ ±----------+

Job方法
fun start(): Boolean
  • 调用该函数来启动这个 Coroutine,如果当前 Coroutine 还没有执行调用该函数返回 true,如果当前 Coroutine 已经执行或者已经执行完毕,则调用该函数返回 false
fun cancel(cause: CancellationException? = null)
  • 通过可选的取消原因取消Job
fun invokeOnCompletion(handler: CompletionHandler): DisposableHandle
  • 通过这个函数可以给 Job 设置一个完成通知,当 Job 执行完成的时候会同步执行这个通知函数。 回调的通知对象类型为:typealias CompletionHandler = (cause: Throwable?) -> Unit.
  • CompletionHandler 参数代表了 Job 是如何执行完成的。 cause 有下面三种情况:
  • 如果 Job 是正常执行完成的,则 cause 参数为 null
  • 如果 Job 是正常取消的,则 cause 参数为 CancellationException 对象。这种情况不应该当做错误处理,这是任务正常取消的情形。所以一般不需要在错误日志中记录这种情况。
  • 其他情况表示 Job 执行失败了。
  • 这个函数的返回值为 DisposableHandle 对象,如果不再需要监控 Job 的完成情况了, 则可以调用 DisposableHandle.dispose 函数来取消监听。如果 Job 已经执行完了, 则无需调用 dispose 函数了,会自动取消监听。
suspend fun join()(suspend函数)
  • 用来在另外一个 Coroutine 中等待 job 执行完成后继续执行。
Job异常传播
  • 协程是有父子级的概念,如果子Job在运行过程之中发生异常,那么父Job就会感知到并抛出异常。如果要抑制这种行为就需要使用SupervisorJob

除了CancellationException以外的异常

SupervisorJob

fun main(){
val parentJob = GlobalScope.launch {
//childJob是一个SupervisorJob
val childJob = launch(SupervisorJob()){
throw NullPointerException()
}
childJob.join()
println(“parent complete”)
}
Thread.sleep(1000)
}

此时childJob抛出异常并不会影响parentJob的运行,parentJob会继续运行并输出parent complete。

3.2、CoroutineDispatcher Element

  • 用于指定协程的运行线程
  • kotlin已经内置了CoroutineDispatcher的4个实现,可以通过Dispatchers的Default、IO、Main、Unconfined字段分别返回使用

public actual object Dispatchers {
@JvmStatic
public actual val Default: CoroutineDispatcher = createDefaultDispatcher()
@JvmStatic
public val IO: CoroutineDispatcher = DefaultScheduler.IO
@JvmStatic
public actual val Unconfined: CoroutineDispatcher = kotlinx.coroutines.Unconfined
@JvmStatic
public actual val Main: MainCoroutineDispatcher get() = MainDispatcherLoader.dispatcher
}

Default,IO

Default,IO其实内部用的是一个线程池,下面逐个解析,看实现原理

default
  • Default会根据useCoroutinesScheduler属性(默认为true)去获取对应的线程池
  • DefaultScheduler(useCoroutinesScheduler=ture):kotlin自己实现的线程池逻辑
  • CommonPool(useCoroutinesScheduler=false):java类库中的Executor实现线程池逻辑

internal actual fun createDefaultDispatcher(): CoroutineDispatcher =
if (useCoroutinesScheduler) DefaultScheduler else CommonPool
internal object DefaultScheduler : ExperimentalCoroutineDispatcher() {

}
//委托类
public open class ExperimentalCoroutineDispatcher(
private val corePoolSize: Int,
private val maxPoolSize: Int,
private val idleWorkerKeepAliveNs: Long,
private val schedulerName: String = “CoroutineScheduler”
) : ExecutorCoroutineDispatcher() {
}
//java类库中的Executor实现线程池逻辑
internal object CommonPool : ExecutorCoroutineDispatcher() {}
//共同父类,定义行为
public abstract class ExecutorCoroutineDispatcher: CoroutineDispatcher(), Closeable {}

ExperimentalCoroutineDispatcher
  • DefaultScheduler的主要实现都在它的父类ExperimentalCoroutineDispatcher中

public open class ExperimentalCoroutineDispatcher(
private val corePoolSize: Int,
private val maxPoolSize: Int,
private val idleWorkerKeepAliveNs: Long,
private val schedulerName: String = “CoroutineScheduler”
) : ExecutorCoroutineDispatcher() {
public constructor(
corePoolSize: Int = CORE_POOL_SIZE,
maxPoolSize: Int = MAX_POOL_SIZE,
schedulerName: String = DEFAULT_SCHEDULER_NAME
) : this(corePoolSize, maxPoolSize, IDLE_WORKER_KEEP_ALIVE_NS, schedulerName)

…//省略构造

//创建CoroutineScheduler实例
private fun createScheduler() = CoroutineScheduler(corePoolSize, maxPoolSize, idleWorkerKeepAliveNs, schedulerName)

override val executor: Executorget() = coroutineScheduler

override fun dispatch(context: CoroutineContext, block: Runnable): Unit =
try {
//dispatch方法委托到CoroutineScheduler的dispatch方法
coroutineScheduler.dispatch(block)
} catch (e: RejectedExecutionException) {

}

override fun dispatchYield(context: CoroutineContext, block: Runnable): Unit =
try {
//dispatchYield方法委托到CoroutineScheduler的dispatchYield方法
coroutineScheduler.dispatch(block, tailDispatch = true)
} catch (e: RejectedExecutionException) {

}

internal fun dispatchWithContext(block: Runnable, context: TaskContext, tailDispatch: Boolean) {
try {
//dispatchWithContext方法委托到CoroutineScheduler的dispatchWithContext方法
coroutineScheduler.dispatch(block, context, tailDispatch)
} catch (e: RejectedExecutionException) {

}
}
override fun close(): Unit = coroutineScheduler.close()
//实现请求阻塞
public fun blocking(parallelism: Int = BLOCKING_DEFAULT_PARALLELISM): CoroutineDispatcher {
require(parallelism > 0) { “Expected positive parallelism level, but have $parallelism” }
return LimitingDispatcher(this, parallelism, null, TASK_PROBABLY_BLOCKING)
}
//实现请求数量限制
public fun limited(parallelism: Int): CoroutineDispatcher {
require(parallelism > 0) { “Expected positive parallelism level, but have KaTeX parse error: Expected 'EOF', got '}' at position 14: parallelism" }̲ require(parall…corePoolSize), but have $parallelism” }
return LimitingDispatcher(this, parallelism, null, TASK_NON_BLOCKING)
}

…//省略一些供测试的方法,更好的跟踪同步状态
}

IO
  • IO的实现其实是LimitingDispatcher

val IO: CoroutineDispatcher = LimitingDispatcher(
this,
systemProp(IO_PARALLELISM_PROPERTY_NAME, 64.coerceAtLeast(AVAILABLE_PROCESSORS)),
“Dispatchers.IO”,
TASK_PROBABLY_BLOCKING
)

LimitingDispatcher
  • IO的实现类会有一些最大请求限制,以及队列处理

private class LimitingDispatcher(
private val dispatcher: ExperimentalCoroutineDispatcher,
private val parallelism: Int,
private val name: String?,
override val taskMode: Int
) : ExecutorCoroutineDispatcher(), TaskContext, Executor {
//同步阻塞队列
private val queue = ConcurrentLinkedQueue()
//cas计数
private val inFlightTasks = atomic(0)

override fun dispatch(context: CoroutineContext, block: Runnable) = dispatch(block, false)

private fun dispatch(block: Runnable, tailDispatch: Boolean) {
var taskToSchedule = block
while (true) {

if (inFlight <= parallelism) {
//LimitingDispatcher的dispatch方法委托给了DefaultScheduler的dispatchWithContext方法
dispatcher.dispatchWithContext(taskToSchedule, this, tailDispatch)
return
}
…//省略了一些队列处理逻辑
}
}
}

CoroutineScheduler
  • Default、IO其实都是共享CoroutineScheduler线程池,Kotlin实现了一套线程池两种调度策略
  • 通过内部的mode区分

fun dispatch(block: Runnable, taskContext: TaskContext = NonBlockingContext, tailDispatch: Boolean = false) {

if (task.mode == TASK_NON_BLOCKING) {
if (skipUnpark) return
signalCpuWork()
} else {
signalBlockingWork(skipUnpark = skipUnpark)
}
}

Mode
TypeMode
DefaultTASK_NON_BLOCKING
IOTASK_PROBABLY_BLOCKING
处理策略
TypeMode
DefaultCoroutineScheduler最多有corePoolSize个线程被创建,corePoolSize它的取值为max(2, CPU核心数)
即它会尽量的等于CPU核心数
IO创建比corePoolSize更多的线程来运行IO型任务,但不能大于maxPoolSize
1.公式:max(corePoolSize, min(CPU核心数 * 128, 2^21 - 2)),即大于corePoolSize,小于2^21 - 2
2.2^21 - 2是一个很大的数约为2M,但是CoroutineScheduler是不可能创建这么多线程的,所以就需要外部限制提交的任务数
3.Dispatchers.IO构造时就通过LimitingDispatcher默认限制了最大线程并发数parallelism为max(64, CPU核心数),即最多只能提交parallelism个任务到CoroutineScheduler中执行,剩余的任务被放进队列中等待。
适合场景
TypeMode
Default1.CPU密集型任务的特点是执行任务时CPU会处于忙碌状态,任务会消耗大量的CPU资源
2.复杂计算、视频解码等,如果此时线程数太多,超过了CPU核心数,那么这些超出来的线程是得不到CPU的执行的,只会浪费内存资源
3.因为线程本身也有栈等空间,同时线程过多,频繁的线程切换带来的消耗也会影响线程池的性能
4.对于CPU密集型任务,线程池并发线程数等于CPU核心数才能让CPU的执行效率最大化
IO1.IO密集型任务的特点是执行任务时CPU会处于闲置状态,任务不会消耗大量的CPU资源
2.网络请求、IO操作等,线程执行IO密集型任务时大多数处于阻塞状态,处于阻塞状态的线程是不占用CPU的执行时间
3.此时CPU就处于闲置状态,为了让CPU忙起来,执行IO密集型任务时理应让线程的创建数量更多一点,理想情况下线程数应该等于提交的任务数,对于这些多创建出来的线程,当它们闲置时,线程池一般会有一个超时回收策略,所以大部分情况下并不会占用大量的内存资源
4.但也会有极端情况,所以对于IO密集型任务,线程池并发线程数应尽可能地多才能提高CPU的吞吐量,这个尽可能地多的程度并不是无限大,而是根据业务情况设定,但肯定要大于CPU核心数。
Unconfined
  • 任务执行在默认的启动线程。之后由调用resume的线程决定恢复协程的线程。

internal object Unconfined : CoroutineDispatcher() {
//为false为不需要dispatch
override fun isDispatchNeeded(context: CoroutineContext): Boolean = false

override fun dispatch(context: CoroutineContext, block: Runnable) {
// 只有当调用yield方法时,Unconfined的dispatch方法才会被调用
// yield() 表示当前协程让出自己所在的线程给其他协程运行
val yieldContext = context[YieldContext]
if (yieldContext != null) {
yieldContext.dispatcherWasUnconfined = true
return
}
throw UnsupportedOperationException("Dispatchers.Unconfined.dispatch function can only be used by the yield function. " +
"If you wrap Unconfined dispatcher in your code, make sure you properly delegate " +
“isDispatchNeeded and dispatch calls.”)
}
}

  • 每一个协程都有对应的Continuation实例,其中的resumeWith用于协程的恢复,存在于DispatchedContinuation
DispatchedContinuation
  • 我们重点看resumeWith的实现以及类委托

internal class DispatchedContinuation(
@JvmField val dispatcher: CoroutineDispatcher,
@JvmField val continuation: Continuation
) : DispatchedTask(MODE_UNINITIALIZED), CoroutineStackFrame, Continuation by continuation {

override fun resumeWith(result: Result) {
val context = continuation.context
val state = result.toState()
if (dispatcher.isDispatchNeeded(context)) {
_state = state
resumeMode = MODE_ATOMIC
dispatcher.dispatch(context, this)
} else {
executeUnconfined(state, MODE_ATOMIC) {
withCoroutineContext(this.context, countOrElement) {
continuation.resumeWith(result)
}
}
}
}

}

解析如下:

  1. DispatchedContinuation通过类委托实现了在resumeWith()方法之前的代码逻辑添加

  2. 通过isDispatchNeeded(是否需要dispatch,Unconfined=false,default,IO=true)判断做不同处理

  3. true:调用协程的CoroutineDispatcher的dispatch方法(上文有解析)

  4. false:调用executeUnconfined方法

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数初中级安卓工程师,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年最新Android移动开发全套学习资料》送给大家,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
img
img
img
img

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频
如果你觉得这些内容对你有帮助,可以添加下面V无偿领取!(备注Android)
img

总结

算法知识点繁多,企业考察的题目千变万化,面对越来越近的“金九银十”,我给大家准备好了一套比较完善的学习方法,希望能帮助大家在有限的时间里尽可能系统快速的恶补算法,通过高效的学习来提高大家面试中算法模块的通过率。

这一套学习资料既有文字档也有视频,里面不仅仅有关键知识点的整理,还有案例的算法相关部分的讲解,可以帮助大家更好更全面的进行学习,二者搭配起来学习效果会更好。

部分资料展示:




有了这套学习资料,坚持刷题一周,你就会发现自己的算法知识体系有明显的完善,离大厂Offer的距离更加近。

资料获取方法:点赞+关注+转发,然后进入我的【GitHub】,里面有免费获取途径

习来提高大家面试中算法模块的通过率。

这一套学习资料既有文字档也有视频,里面不仅仅有关键知识点的整理,还有案例的算法相关部分的讲解,可以帮助大家更好更全面的进行学习,二者搭配起来学习效果会更好。

部分资料展示:

[外链图片转存中…(img-wGQkqI07-1710667592265)]
[外链图片转存中…(img-N3VXhYhD-1710667592265)]
[外链图片转存中…(img-wTRkadR5-1710667592266)]
[外链图片转存中…(img-UHDaxYd0-1710667592266)]

有了这套学习资料,坚持刷题一周,你就会发现自己的算法知识体系有明显的完善,离大厂Offer的距离更加近。

资料获取方法:点赞+关注+转发,然后进入我的【GitHub】,里面有免费获取途径

  • 17
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值