侧边栏壁纸
博主头像
Z同学博主等级

工作磨平激情前,坚持技术的热忱。 欢迎光临Z同学的技术小站。 分享最新的互联网知识。

  • 累计撰写 274 篇文章
  • 累计创建 55 个标签
  • 累计收到 74 条评论

Kotlin 协程 基本知识

Z同学
2021-10-19 / 2 评论 / 4 点赞 / 293 阅读 / 4,089 字
温馨提示:
本文最后更新于 2022-05-07,若内容或图片失效,请留言反馈。部分素材来自网络,若不小心影响到您的利益,请联系我们删除。

前言

协程,并不是Kotlin的标准库中提供的API。是JetBrains开发的协程库。

JetBrains开发了一个针对线程处理的协程库:kotlinx.coroutines

你可以理解协程库是kotlin的一个扩展库。丰富Kotlin中的线程处理逻辑。类似java中很多大量的第三方公司开发的jar等库。所以我们如果使用协程需要额外导入相关依赖库。

那么协程库能干嘛?也许等我们深入了解之后,才能回答了。

依赖

因为协程是一个独立库,所以我们需要手动添加相关依赖库

示例:

我们依赖后就可以简单使用了

dependencies {
    ...
    implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.4.2'
}

如果是在Android中使用。那么我们还需要依赖:

implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.4.2'
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.4.2'

Hello World

示例:

import kotlinx.coroutines.*

fun main(string: Array<String>) {

    GlobalScope.launch {//创建一个后台的 协程
        delay(1000L) //非阻塞等待1秒(默认单位毫秒)
        print("world ") //输出内容
    }
    print("Hello ")
    Thread.sleep(2000L)// 阻塞主程序,2秒(默认单位毫秒)
}
//输出
Hello world 

在上面我们有注意的地方: 1.import 导入的库,我们如果使用GloabalScope就需要该库了。

上面的效果,会先打印Hello 然后等待差不多2秒就会打印下面的world。

协程本质上是一个轻量级的线程。

上面的例子,我们如果不用协成,是否可以直接使用Kotlin的原生API实现呢?

答案当然是可以了。

示例:

import kotlin.concurrent.thread

fun main(string: Array<String>) {

    thread {
        Thread.sleep(1000L)
        print("world ") //输出内容
    }
    print("Hello ")
    Thread.sleep(2000L)// 阻塞主程序,2秒(默认单位毫秒)
}

其实这上面的Threadjava 的库啊。请不要理解错误了。

但是thread{}kotlin.concurrent 里面的api。

小提示:delay() 是一个协程的挂起函数。不会造成线层阻塞。并仅限于在协程中使用。

我们如果创建一个协程的持有对象会怎么样。

示例:

import kotlinx.coroutines.*

suspend fun main() {
    val job= GlobalScope.launch { // 启动一个新协程并保持对这个作业的引用
        delay(1000L)
        println("World!")
    }
    println("Hello,")
    job.join() //等待子协程执行结束
}
//输出
Hello,
World!

该方法join的话。那么需要添加个suspend关键字。否则错误。

注意事项

协程在实际使用中有很多需要注意的。当我们使用 GlobalScope.launch 时,我们会创建一个顶层协程。虽然它很轻量,但它运行时仍会消耗一些内存资源。如果我们忘记保持对新启动的协程的引用,它还会继续运行。如果协程中的代码挂起了会怎么样(例如,我们错误地延迟了太长时间),如果我们启动了太多的协程并导致内存不足会怎么样? 必须手动保持对所有已启动协程的引用并 执行join很容易错误。

解决方法

在代码中使用结构化并发。在作用域内启动协程。而不是像我们以前java中使用线程那样,(线程总是全局的)在GlobalScope中启动。

所以上面只是介绍了什么是协程。最简单的调用逻辑。

如果不太懂,没有关系。我们再之后的学习中可以加深理解。

积累足够的时候,我们自然就能理解了。

示例:在作用域中启动一个新协程

import kotlinx.coroutines.*

fun main() = runBlocking { // this: CoroutineScope
    launch { // 在 runBlocking 作用域中启动一个新协程
        delay(1000L)
        println("World!")
    }
    println("Hello,")
}
//输出
Hello,
World!

作用域构建器

常见的作用域会创建一个协程作用域并且在所有已启动子协程执行完毕之前不会结束。

  • runBlocking: 方法会阻塞当前线程来等待。(常规函数)
  • coroutineScope:方法会挂起,释放底层线程用于其他用途。(挂起函数)

示例:

import kotlinx.coroutines.*

fun main() = runBlocking { // this: CoroutineScope
    launch { // 在 runBlocking 作用域中启动一个新协程
        delay(500L)
        println("这个Task 是runBlocking!")
    }
    coroutineScope {
        launch {
            delay(1000L)
            println("这个Task 是一个 nested Launch")
        }
        delay(200L)
        println("这个Task  是一个coroutine scope")
    }
    println("作用域结束")
}
//输出
这个Task  是一个coroutine scope
这个Task 是runBlocking!
这个Task 是一个 nested Launch
作用域结束

重构协程函数 suspend

我们上面的例子,可以进行重构,将launch里面的代码提取到独立的函数中。

这个独立的函数,就会需要添加suspend 关键字。它所修饰的就是一个挂起的函数。

我们可以在协程内部像使用普通函数一样使用挂起函数。

示例:

import kotlinx.coroutines.*

fun main() = runBlocking { // this: CoroutineScope
    launch {
        doYan()
    }
    print("zin")
}

suspend fun doYan() {
    delay(100L) //挂起100毫秒
    print("yan小站欢迎你")
}
//输出
zinyan小站欢迎你

协程那份复杂,为什么不用线程呢?

因为很简单,协程效率不线程高效,同时也不那么容易造成内存溢出

示例:

import kotlinx.coroutines.*

fun main() = runBlocking {
    repeat(100_000) { // 启动大量的协程
        launch {
            delay(5000L)
            print(".")
        }
    }
}
//输出
..........................................................................................................................................................................................................省略了后面的

这个命令,启动了10万个协程,并且在5秒后,输出一个点:.

使用协程我们能够正常顺利的打印出来。如果是线程?创建10万个线层就得电脑内存崩溃了。

所以:协程很轻。

全局协程 GlobalScope

我们在作用域上说了,协程如果是GlobalScope创建的话,就是一个全局协程,将会一直运行。容易占用内存。并且不知道的情况下,如果创建了大量全局协程也不方便进行维护管理。

但是某种情况下,也比较适合使用全局协程。那就是创建守护线程。

可以将全局协程当守护线程来利用。

示例:

fun main(string: Array<String>) {
    GlobalScope.launch {//创建一个后台的 协程
        repeat(100) { i ->
            println("Zinyan: sleeping $i")
            delay(500L)
        }
    }
    println("Hello 欢迎来到Z同学的小站:")
    Thread.sleep(5000L)// 阻塞主程序,5秒(默认单位毫秒)
    println("程序结束")
}
//输出
Hello 欢迎来到Z同学的小站:
Zinyan: sleeping 0
Zinyan: sleeping 1
Zinyan: sleeping 2
Zinyan: sleeping 3
Zinyan: sleeping 4
Zinyan: sleeping 5
Zinyan: sleeping 6
Zinyan: sleeping 7
Zinyan: sleeping 8
Zinyan: sleeping 9
程序结束

我们可以看到,在阻塞的5秒时间中,每500毫秒,我们就打印一个输出。但是并没有循环到100次就结束了。

GlobalScope中的启动的协程并不会使整个进程保活。它就像守护线程。

进程还活着的情况下,我能正常执行,当进程销毁时我也会销毁。

而不像有些线层逻辑,会一直占用进程,而造成进程无法关闭。甚至内存溢出。

吐槽

学习到现在,我脑子里只有一个映像,协程就是不会内存溢出的线层呗。也不知道理解的对不对。

4

评论区