Kotlin Coroutines(协程) 完全解析系列:
Kotlin Coroutines(协程) 完全解析(一),协程简介
Kotlin Coroutines(协程) 完全解析(二),深入理解协程的挂起、恢复与调度
Kotlin Coroutines(协程) 完全解析(三),封装异步回调、协程间关系及协程的取消
Kotlin Coroutines(协程) 完全解析(四),协程的异常处理
Kotlin Coroutines(协程) 完全解析(五),协程的并发
本文基于 Kotlin v1.3.0-rc-146,Kotlin-Coroutines v1.0.0-RC1
通过前面几篇文章可以明白协程就是可以挂起和恢复执行的运算逻辑,挂起函数用状态机的方式用挂起点将协程的运算逻辑拆分为不同的片段,每次运行协程执行的不同的逻辑片段。所以协程在运行时只是线程中的一块代码,线程的并发处理方式都可以用在协程上。不过协程还提供两种特有的方式,一是不阻塞线程的互斥锁Mutex
,一是通过 ThreadLocal 实现的协程局部数据。
1. Mutex
线程中锁都是阻塞式,在没有获取锁时无法执行其他逻辑,而协程可以通过挂起函数解决这个,没有获取锁就挂起协程,获取后再恢复协程,协程挂起时线程并没有阻塞可以执行其他逻辑。这种互斥锁就是Mutex
,它与synchronized
关键字有些类似,还提供了withLock
扩展函数,替代常用的mutex.lock; try {...} finally { mutex.unlock() }
:
|
|
Mutex
的使用比较简单,不过需要注意的是多个协程竞争的应该是同一个Mutex
互斥锁。
2. 协程局部数据
线程中可以使用ThreadLocal作为线程局部数据,每个线程中的数据都是独立的。协程中可以通过ThreadLocal.asContextElement()
扩展函数实现协程局部数据,每次协程切换会恢复之前的值。先看下面的示例:
|
|
输出如下:
|
|
上面的输出有个疑问的地方,为什么执行yield()
挂起函数后 threadLocal 的值不是launch changed
而变回了launch
?
下面直接分析源码:
|
|
根据上面的源码和断点调试,可以发现协程的启动和恢复都会执行一次ThreadContextElement.updateThreadContext(context)
和ThreadContextElement.restoreThreadContext(context, oldValue)
,现在再分析一次上面的代码运行:
|
|
所以 ThreadContextElement 并不能跟踪所有ThreadLocal
对象的访问,而且每次挂起时更新的值将丢失。最重要的牢记它的原理:启动和恢复时保存ThreadLocal
在当前线程的值,并修改为 value,挂起和结束时修改当前线程ThreadLocal
的值为之前保存的值。
3. 已有线程同步方式
AtomicInteger 等
java.util.concurrent.atomic
包中的原子类ConcurrentHashMap 等线程安全的集合
协程中的并发与线程的并发大部分是相同的,所以本篇文章应该是目前为止该系列文章中最容易理解的一篇,本系列Kotlin Coroutines(协程) 完全解析暂时就到这里,后面待 select 表达式、Channel、Actor 等实验性内容正式发布后继续解析,还有在 Android 项目中协程的实际运用,敬请期待。