Kotlin(十八)协程(基础)

一.第一个程序

fun main() {
    GlobalScope.launch {//在后台启动一个协程
        delay(1000)//非阻塞的等待1S钟
        println("world")//1S钟之后打印 world
    }
    //协程在等待的时候,主线程还在继续执行,
    //主线程打印hello
    println("hello")
    //主线程阻塞2S,保证jvm存活
    Thread.sleep(2000)

}
hello
world

通过GlobalScope.launch启动了一个新的协程,协程也可以称之为轻量级的线程,这些线程在GlobalScope上下文中与协程构建器一起启动。delay是一个特殊的挂起函数,它不会造成线程的阻塞,但是会挂起协程,并且只能在协程中使用。

二.桥接阻塞与非阻塞的世界

以上的例子中,我们同时使用了非阻塞等待的delay和阻塞等待的sleep。sleep是属于java中的api。我们也可以通过协程的api进行阻塞等待。使用runBlocking+delay

package com.example.kotlin01

import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking

fun main() {
    GlobalScope.launch {//在后台启动一个协程
        delay(1000)//非阻塞的等待1S钟
        println("world")//1S钟之后打印 world
    }
    //协程在等待的时候,主线程还在继续执行,
    //主线程打印hello
    println("hello")
    //主线程阻塞2S,保证jvm存活
    runBlocking {
        delay(2000)
    }

}
hello
world

runBlocking+delay的方式替换了以上的sleep。也能达到一样的效果,阻塞等待。
另外一种习惯的写法

package com.example.kotlin01

import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking

fun main() = runBlocking {

    GlobalScope.launch {
        delay(1000)
        println("world")//1S钟之后打印 world
    }
    println("hello")
    delay(2000)
}

runBlocking作为启动顶层协程的适配器。

三.等待一个作业

以上我们都是通过阻塞主线程等待一段时间,等待另外协程执行完。这不是一个很好的选择,因为在实际开发中,我们不一定能掌握协程执行需要的具体时间,并且这样还造成了主线程的阻塞。那么我们可以通过非阻塞的方式等待所启动的协程执行完毕。

suspend fun main() {
    val job = GlobalScope.launch {
        delay(1000)
        println("world")
    }
    println("hello")
    job.join()// 等待直到子协程执行结束
}

hello
world

实际结果就是先在主线程中执行了hello,然后等待job协程的等待1S执行打印world。最后结束。我们通过join()函数来等待一个协程执行完然后结束。最后的结果和之前例子结果是一样的,但是区别在于这种方式主线程于后台作业的时间没有了关联关系。

四. 结构化的并发

当我们使用GlobalScope.launch,我们会创建一个顶层协程,也会消耗内存。如果我们忘记保持对新协程的引用,它还会继续执行。如果我们启动了太多的协程,没有保持对已启动的协程的引用并join结束掉。会容易导致内存不足。以上的例子我们是通过join()的方式结束掉协程。我们也可以作用域构建器构建一个区域,将协程的实例添加在所在的区域中。这样我们就无需显示的join。

五. 作用域构建器

1.使用runBlocking构建器
fun main() = runBlocking {
    launch {// 在 runBlocking 作用域中启动一个新协程
        delay(1000)
        println("world")
    }
    println("hello")
}

hello
world

以上我们通过runBlocking 构建一个作用区域,并通过launch启动一个新的协程。在runBlocking 作用区域内的代码块执行完毕之后,会隐式的结束(join)掉协程。

2.使用coroutineScope构建器
suspend fun main() = coroutineScope {
    launch {
        delay(1000)
        println("world")
    }
    println("hello")

}

````java
hello
world

coroutineScope的方式效果也一样。他们俩的本质区别是runBlocking 的方式会阻塞主线程等待。coroutineScope 的方式只是挂起,会释放底层的线程用于其他用途。不会造成主线程的阻塞。

六. 提取函数重构

我们将launch 中执行的代码块,抽出独立到一个函数。

 fun main() {
    test4()
}

fun test4() = runBlocking {
    launch {
        test3()
    }
    println("hello")
}

 suspend fun test3(){
    delay(1000)
    println("world")
}
hello
world

执行结果一个,在独立的函数中我们添加了suspend 修饰符


suspend fun main() {
    test4()
}

suspend fun test4() = coroutineScope {
    launch {
        test3()
    }
    println("hello")
}

 suspend fun test3(){
    delay(1000)
    println("world")
}
hello
world

如果我们使用的是coroutineScope ,那么所有的函数都需要用suspend 修饰。

六. 协程很轻量

我们看以下代码

 fun main() {
    test4()
}

 fun test4() = runBlocking {
     repeat(100_000) { // 启动大量的协程
         launch {
             delay(5000L)
             print(".")
         }
     }
}

启动了一万个协程。但是如果在线程这样操作的话,可能会导致内存不足

七.全局协程像守护线程

我们回到最开始创建协程的方式

package com.example.kotlin01

import kotlinx.coroutines.*

suspend fun main() {
    test3()
}

suspend fun test3() {
    GlobalScope.launch {
        repeat(1000) { i ->
            println("I'm sleeping $i ...")
            delay(500L)
        }
    }
    delay(1300L) // 在延迟后退出
}
I'm sleeping 0 ...
I'm sleeping 1 ...
I'm sleeping 2 ...

在协程中打印了三次之后,全局协程阻塞完毕,协程内打印结束。就类似java中的线程守护了1300毫秒。

你可能感兴趣的:(Kotlin(十八)协程(基础))