前语
- 协程是轻量级的线程
- 协程又称微线程
- 协程(Coroutines),是基于线程之上,但又比线程愈加轻量级的存在,这种由程序员自己写程序来办理的轻量级线程叫做用户空间线程,具有对内核来说不行见的特性。
- 协程,又名纤程(Fiber),或者绿色线程(GreenThread)
协程是一种协作式的代码程序,能够让开发人员自己去进行人为操控的一种结构。一种并发理念,在Kotlin开发语言的协程仍是和其他平台不同的。
协程、协作
Kotlin的协程是在线程基础上封装的线程结构。
JVM当中,线程是CPU调度的基本单位,它运转在内核态,线程的履行不能靠Java开发人员的人为的干涉,即便能够进步线程的优先级,也不能确保线程必定优先履行,线程是抢占式的。(线程和协程不是同级其他)
// 界说一个线程,优先级为50,而且发动它
thread(priority = 50) {
Log.i(TAG,"start thread 1")
}
// 界说一个线程,优先级为100,而且发动它
thread(priority = 100) {
Log.i(TAG,"start thread 2")
}
通过几回运转,他们的次序是不能确保的。所以线程是抢占式的。
// 界说一个线程,优先级为50,而且发动它
thread(priority = 50) {
Log.i(TAG,"start thread 1")
// 界说一个线程,优先级为100,而且发动它
thread(priority = 100) {
Log.i(TAG,"start thread 2")
}
}
这样写能够确保线程的履行次序。
fun main()= runBlocking {
// 发动第一个协程
val job1 = launch(Dispatchers.Default) {
repeat(10){
log("job1:$it")
delay(200)
}
}
// 等候协程1完结
job1.join()
// 发动第2个协程
val job2 = launch(Dispatchers.Default) {
repeat(10){
log("job2:$it")
}
}
// 等候协程2完结
job2.join()
log("main All jobs are completed.")
}
操控协程履行前后次序。协程本身是基于线程的,它的履行次序也是依靠线程的,在开发层面上,协程是一个封装好的线程池结构,它供给丰厚的API结合kotlin编译器语法帮忙更好处理异步帮忙编程。之前每协程的时分,依靠线程池、Handler、AsyncTask、IntentService等等异步编程的结构来完成咱们的代码,在帮忙办法上,协程帮咱们封装了各个或许遇到的场景的线程结构。
协程确保协作的重要的两个概念,挂起和康复。
挂起和康复
线程挂起是指暂停当时线程的履行,将操控权交给其他线程或进程。一般发生在多线程中,当一个线程需求等候某个条件成立(例如:等候资源可用或等候其他线程完结作业)时,它能够挑选挂起自己,让出CPU给到其他线程运用。
运用wait和notify/notifyAll组合线程挂起和康复。
public static void test(){
// 界说一个lock变量来完成锁的竞赛
final Object lock = new Object();
Thread thread1 = new Thread(() -> {
// 线程1获取锁
synchronized (lock){
try{
log("thread1 wait");
lock.wait();
log("thread2 resumed");
}catch(InterruptedException e){
e.printStackTrace();
}
}
});
Thread thread2 = new Thread(() -> {
// 线程2获取锁
synchronized (lock) {
try {
log("thread2 sleep 5seconds");
Thread.sleep(5000);// 模仿耗时操作
log("thread2 notify");
lock.notify();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
thread1.start();
thread2.start();
}
fun main()= runBlocking {
ThreadExample.test()
// 避免父线程过早退出
Thread.sleep(5500)
}
输出:
thread1 wait
thread2 sleep 5seconds
thread2 notify
thread2 resumed
不过,java的wait实际上是会堵塞当时线程。
// 界说请求办法
fun requestHttp(
requestParams : Map<String,String>,
block : (RequestResult) -> Unit
) = requestHttpReal(requestParams,block) // 这儿模仿实在的请求
val requestParams = assembleRequestParams() // 这儿是主线程
thread { // 这儿会“挂起”当时的线程
requestHttp(requestParams){ // 这儿是IO线程,进行http请求
requestResult->{
runOnUiThread{ updateUI(requestResult) } // 这儿会“康复”当时的线程,切换回主线程,进行UI刷新
}
}
}
doSomethingElse()
看似主线程被挂起,其实主线程还在履行(下面的doSomethindelse)。
协程的挂起和康复
在需求的办法上加上suspend要害字。
suspend fun requestHttp(
requestParams : Map<String,String>,
block : (RequestResult) -> Unit
) = requestHttpReal(requestParams,block) // 这儿模仿实在的请求
val requestParams = assembleRequestParams()
GlobalScope.launch{ // 敞开一个全局协程Coroutine-1(实际开发环境当中不建议运用) ,在main主线程
val requestResult = withContext(Dispatchers.IO){ // 敞开一个协程Coroutine-2,并将该协程放在异步线程里边履行,并挂起Coroutine-1
requestHttp(requestParams) // 非堵塞的等候 1 秒钟(默认时间单位是毫秒),该办法用suspend要害字润饰
}
updateUI(requestResult) // 康复Coroutine-1的履行,进行UI刷新
}
doSomethingElse()
kotlin编译器结合协程,让咱们用同步的办法写异步代码(updateUi会等候withContext里协程履行完耗时操作后才履行),避免回调阴间。
- 运用GlobalScope.launch创立协程Coroutine-1,虽然这个协程在主线程运转,可是他具备了协程的挂起和康复的能力。
- 当履行withContext(Dispatchers.IO)时分,敞开了第二个协程Couroutine-2
- 由于withContext函数的特性,它会挂起当时协程(Coroutine-1),此时会将咱们的履行点进行一个上下文记载(包括协程履行的代码点,所在的线程、它的上下文数据),也便是updatUI之前,中止(协程coroutine-1)的履行,转而履行Coroutine-2里边的代码,也便是requestHttp的代码
- 由于withContext里指定的调度器是Dispatchers.IO,协程结构会主动进行线程的切换
- 等候requestHttp代码履行完毕,根据之前Coroutine-1的记载的代码点和所在的线程,从头回到updateUi代码上根据之前记载的所在线程持续履行,这儿的协程结构也会帮咱们把协程主动切换回主线程。
- doSomethingElse不在协程内部,所以他不会被挂起,他不会等地啊withContext里边的代码,而是直接持续履行。
上面的代码和runOnUIThread的Handler相似。
协程(协程体)实质上是一段代码结构,能够理解为Runnable,这个”Runnable”比较特殊,协程结构记载上下文(包括:代码履行位置、所在的线程、其他额外资源),在需求的时分从头唤起。协程的调度器相似于线程池相同。编译器和协程的结构帮咱们做了这件事。协程的康复,实质便是callback,他会将后面还没履行完结的那部分代码打包成一个callback,然后持续履行。callback封装相似:
fun coroutineRequestCallback(requestResult : RequestRequest?){
requestResult?.let{
updateUI(requestResult)
}
}
requestHttp办法,在协程运用的时分加上了suspend润饰符(suspend润饰的办法只能被协程或supsend润饰的办法调用)。
suspend是kotlin引入的一个要害字,当时的办法是一个挂起函数,只有被suspend润饰的办法,他才或许被挂起(不必定会被挂起)。
挂起必要条件:
异步调用是否发生,取决于resume函数与对应的挂起函数调用是否在相同的调用栈上,切换函数调用栈的办法能够是切换到其他线程上履行,也能够不是切换线程但在当时函数回来后的某一个时间再履行。后者其实一般是将Continuation的实例保存下来,再后续合适的机遇再调用,存在事情循环的平台很简单做到。例如:Android平台主线程Looper
- resume函数与对应的挂起函数的调用是否在相同的调用栈上(resume函数代表康复被挂起的线程,然后将成果回来)
- 切换函数调用栈的办法能够是切换到其他线程上履行
- 也能够是不切换线程但在当时函数回来之后的某一个时间再履行。例如:Andorid里的handler。
kotlin标准的协程库运用办法:
// 1 结构协程体,这儿是挂起函数,运用suspend符号当时的函数体答应被挂起
val f : suspend () -> Int = {
log("In Coroutine.")
999
}
// 2 协程体内代码之后,进行的成果
val completion = object : Continuation<Int> {
override fun resumeWith(result: Result<Int>) {
log("Coroutine End: $result")
// 拿到协程回来的成果,这是康复函数,也便是resume
val resultOrThrow = result.getOrThrow()
// todo
}
override val context: CoroutineContext = EmptyCoroutineContext
}
// 3 创立协程
val createCoroutine = f.createCoroutine(completion)
// 4 履行协程
createCoroutine.resume(Unit)
当调用createCoroutine.resume(Unit)时分,协程库会主动调用被suspend符号的block f里边的代码,然后将履行成果给到resumeWith(result:Result)
咱们将suspend对应的函数体去掉(suspend润饰一个空函数),提示suspend modifier。
编译器推荐直接移除suspend。suspend是一个润饰符,虽然告知开发者,这个办法是一个挂起办法,但他不必定会挂起。
delay是一个能够被挂起的函数,他将会resume函数与对应的挂起函数调用放在不同的调用栈上。
delay办法被suspend润饰,且suspend是有用的,调用了suspendCancellableCoroutine
suspendCancelableCoroutine办法被suspend润饰,且suspend是有用的,调用了supendCoroutineUninterceptedOrReturn(是suspend生效),然后挂起协程的要害点。
delay、withContext、suspendCoroutine办法能够顺畅挂起协程的,里边最终调用了supendCoroutineUninterceptedOrReturn办法。
kotlin协程
是一个代码结构体,相似线程池里边的Runnable、Future、以及Handler里边的Message转化出来的Runnable,只不过协程结合了kotlin编译器,封装了供开发者运用的APi的一种线程结构。协程不应该跟线程比较,如:微信小程序和微信运用一般。
协程的挂起和康复是什么
在某一个效果域里,暂停挂起当时协程,去履行另外一个协程,当被履行的协程履行完毕后,从头去康复被挂起的协程的履行,康复的实质是callback,编译器帮咱们做了callback的封装和调用。
suspend要害字效果
是一种结合编译器,提示开发人员,当时办法会被挂起。是否真实挂起,取决于办法体内最终是否会被调用到suspendCoroutineUninterceptedReturn办法,其中delay、withContext、suspendCoroutine都是google开发者封装好的便利挂起的API。
协程比线程功率更高、内存更小吗?
协程和线程比没有意义。协程原本便是在线程里边运转的子使命。就像线程池里边与逆行的Runnable,是HandlerThread里边发送的message对应的callback相同。如:微信小程序和微信的联系。
网上以及官方说,协程只占用4K巨细,远远小于线程,没可比性。没有微信,微信小程序再小,也是无意义的。
为什么挑选协程
协程并没有比线程高效。可是他提升了开发人员的开发功率、快捷、代码逻辑明晰。
长处
- 用同步办法写异步代码,避免回调阴间
- 用协程API供给的办法,非堵塞式挂起和康复操作,完成了对长期运转使命的优雅处理,进步开发功率
- Kotlin协程支持结构化并发,看似堵塞式写法来完成异步功能,避免并发编程常见问题,如:数据竞态和死锁。
- Kotlin协程供给如通道(Channels)、流(Flows)等高级特性,更好处理杂乱的异步流程和数据流。
缺陷
需求深入了解,门槛运用高,写的协程并不正确,没发挥协程优势等。