一.第一个程序
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毫秒。