【Kotlin回顾】9.协程思维模型

语言: CN / TW / HK

theme: cyanosis


开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第9天,点击查看活动详情

1.协程

协程不是进程或线程,它的执行过程更类似于子例程或者说不带返回值的函数调用。

一个程序可以包含多个协程,类似于一个进程包含多个线程。线程有自己的上下文多个线程存在时它们相对独立,切换受系统控制,而协程也相对独立,也有自己的上下文,但是切换是由自己控制的,当需要切换到其他协程时是由当前协程控制的。

| | 线程 | 协程 | | ------- | -------- | ---------- | | 独立性 | 相对独立 | 相对独立 | | 上下文 | 有自己的上下文 | 有自己的上下文 | | 切换 | 系统决定是否切换 | 当前协程决定是否切换 |

2.Kotlin协程

1.引入Kotlin协程

Kotlin中如果要使用协程是需要添加依赖的,它没有被集成在标准库中,单独拎出来主要是为了减小标准库的体积

implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.0'

那么要如何理解Kotlin协程?Kotlin协程可以理解为更轻量级的线程,协程的运行离不开线程,这有点像线程和进程之间的关系。

2.Kotlin协程的使用

``` fun main() = runBlocking { doWorld() }

suspend fun doWorld() = coroutineScope {
launch { delay(1000L) println("World!") } println("Hello") }

//输出结果: //Hello //World! ```

上面的代码是Kotlin协程的一个简单例子其中runBlockingcoroutineScopelaunch是协程作用域,suspend是挂起,delay(1000L)是一个延时函数。从结果来看我们的代码顺序和输出结果的顺序不一样,这就是Kotlin协程的魅力,我们看下面的流程图:

3.Kotlin协程的轻量(总结的还不够清晰)

  • 启动 10 亿个线程,这样的代码运行在大部分的机器上都是会因为内存不足等原因而异常退出的。那么启动10亿个协程则不会出现异常因为协程是非常轻量的;
  • 协程虽然是运行在线程上的但是它并不会与某个线程绑定,而且还可以在不同线程之间切换。

4.协程的“非阻塞式”

线程是阻塞的,因为它在运行一个耗时任务时只有这个任务完成了才会执行后面的任务,而Kotlin在执行一个耗时任务时会把这个任务放入后台执行,去执行下一个任务。

``` fun main() { repeat(3) { Thread.sleep(1000L) println("Print-1:${Thread.currentThread().name}") }

repeat(3) {
    Thread.sleep(900L)
    println("Print-2:${Thread.currentThread().name}")
}

}

/ 输出结果: Print-1:main Print-1:main Print-1:main Print-2:main Print-2:main Print-2:main / ```

上面的代码是阻塞式任务,可以看到两个任务之间的等待时差是100毫秒,但是第二个repeat执行的前提是第一个repeat执行完毕,那么这个任务的最终耗时就是5700毫秒。

``` fun main() = runBlocking { launch { repeat(3) { delay(1000L) println("Print-1:${Thread.currentThread().name}") } }

launch {
    repeat(3) {
        delay(900L)
        println("Print-2:${Thread.currentThread().name}")
    }
}
delay(3000L)

}

/ 输出结果: Print-2:main @coroutine#3 Print-1:main @coroutine#2 Print-2:main @coroutine#3 Print-1:main @coroutine#2 Print-2:main @coroutine#3 Print-1:main @coroutine#2 / ```

上面的代码是非阻塞式任务,可以看到两个任务之间的等待时差是100毫秒,第以个repeat和第二个repeat是同时执行的,也就是说他们同时开始执行,当达到900毫秒时第二个repeat开始执行,当达到1000毫秒时第一个repeat开始执行,那么这个任务的最终耗时就是3000毫秒。

由此可见,Kotlin 协程的“非阻塞”其实只是语言层面的,当我们调用 JVM 层面的 Thread.sleep() 的时候,它仍然会变成阻塞式的。与此同时,这也意味着我们在协程当中应该尽量避免出现阻塞式的行为。尽量使用 delay,而不是 sleep。

到这里其实就会产生一个疑问,delay就能解决阻塞的问题吗?答案是不是,解决阻塞问题的其实是Kotlin的挂起和恢复的能力,这是协程才拥有的特殊能力。

挂起和恢复又该怎么理解呢,举个例子,现在有两件事情:①烧一壶水烧开后断开电源;②做饭;第一件事是一个耗时任务,开始烧水后我去做第二件事这就是挂起,当水烧开以后要去断开电源这就是恢复。

5.建立思维模型

Kotlin的协程要比Java线程更抽象,因为Java的线程可以找到Thread的源码,同时线程也是操作系统中的一个概念,所以理解起来较为简单。而Kotlin的协程没有类似的知识点可以建立关联,所以在学习Kotlin协程的时候就需要简历协程的思维模型, 没有这个思维理解Kotlin协程就比较难。

如何建立Kotlin的思维模型?可以将Kotlin协程想象成一个“更加轻量级的线程”。

从包含关系上看,协程跟线程的关系,有点像线程与进程的关系,毕竟协程不可能脱离线程运行。所以,协程可以理解为运行在线程当中的、更加轻量的 Task