前语

  1. 协程是轻量级的线程
  2. 协程又称微线程
  3. 协程(Coroutines),是基于线程之上,但又比线程愈加轻量级的存在,这种由程序员自己写程序来办理的轻量级线程叫做用户空间线程,具有对内核来说不行见的特性。
  4. 协程,又名纤程(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里协程履行完耗时操作后才履行),避免回调阴间。

  1. 运用GlobalScope.launch创立协程Coroutine-1,虽然这个协程在主线程运转,可是他具备了协程的挂起和康复的能力。
  2. 当履行withContext(Dispatchers.IO)时分,敞开了第二个协程Couroutine-2
  3. 由于withContext函数的特性,它会挂起当时协程(Coroutine-1),此时会将咱们的履行点进行一个上下文记载(包括协程履行的代码点,所在的线程、它的上下文数据),也便是updatUI之前,中止(协程coroutine-1)的履行,转而履行Coroutine-2里边的代码,也便是requestHttp的代码
  4. 由于withContext里指定的调度器是Dispatchers.IO,协程结构会主动进行线程的切换
  5. 等候requestHttp代码履行完毕,根据之前Coroutine-1的记载的代码点和所在的线程,从头回到updateUi代码上根据之前记载的所在线程持续履行,这儿的协程结构也会帮咱们把协程主动切换回主线程。
  6. 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

  1. resume函数与对应的挂起函数的调用是否在相同的调用栈上(resume函数代表康复被挂起的线程,然后将成果回来)
  2. 切换函数调用栈的办法能够是切换到其他线程上履行
  3. 也能够是不切换线程但在当时函数回来之后的某一个时间再履行。例如: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巨细,远远小于线程,没可比性。没有微信,微信小程序再小,也是无意义的。

为什么挑选协程

协程并没有比线程高效。可是他提升了开发人员的开发功率、快捷、代码逻辑明晰。

长处

  1. 用同步办法写异步代码,避免回调阴间
  2. 用协程API供给的办法,非堵塞式挂起和康复操作,完成了对长期运转使命的优雅处理,进步开发功率
  3. Kotlin协程支持结构化并发,看似堵塞式写法来完成异步功能,避免并发编程常见问题,如:数据竞态和死锁。
  4. Kotlin协程供给如通道(Channels)、流(Flows)等高级特性,更好处理杂乱的异步流程和数据流。

缺陷

需求深入了解,门槛运用高,写的协程并不正确,没发挥协程优势等。