阻塞与非阻塞
runBlocking
delay
是非阻塞的,Thread.sleep
是阻塞的。显式使用 runBlocking
协程构建器来阻塞。
import kotlinx.coroutines.*
fun main() {
GlobalScope.launch { // 在后台启动一个新的协程并继续
delay(200)
"rustfisher.com".forEach {
print(it)
delay(280)
}
}
println("主线程中的代码会立即执行")
runBlocking { // 这个表达式阻塞了主线程
delay(3000L) //阻塞主线程防止过快退出
}
println("\n示例结束")
}
可以看到,runBlocking
里使用了delay
来延迟。用了runBlocking
的主线程会一直阻塞直到runBlocking
内部的协程执行完毕。 也就是runBlocking{ delay }
实现了阻塞的效果。
我们也可以用runBlocking
来包装主函数。
import kotlinx.coroutines.*
fun main() = runBlocking {
delay(100) // 在这里可以用delay了
GlobalScope.launch {
delay(100)
println("Fisher")
}
print("Rust ")
delay(3000)
}
runBlocking<Unit>
中的<Unit>
目前可以省略。
runBlocking
也可用在测试中
// 引入junit
dependencies {
implementation("junit:junit:4.13.1")
}
测试
使用@Test
设置测试
import org.junit.Test
import kotlinx.coroutines.*
class C3Test {
@Test
fun test1() = runBlocking {
println("[rustfisher] junit测试开始 ${System.currentTimeMillis()}")
delay(1234)
println("[rustfisher] junit测试结束 ${System.currentTimeMillis()}")
}
}
运行结果
[rustfisher] junit测试开始 1632401800686
[rustfisher] junit测试结束 1632401801928
IDEA可能会提示no tasks available
。需要把测试选项改为IDEA,如下图。
更改设置
等待
有时候需要等待协程执行完毕。可以用join()
方法。这个方法会暂停当前的协程,直到执行完毕。需要用main() = runBlocking
。
import kotlinx.coroutines.*
fun main() = runBlocking {
println("[rustfisher]测试等待")
val job1 = GlobalScope.launch {
println("job1 start")
delay(300)
println("job1 done")
}
val job2 = GlobalScope.launch {
println("job2 start")
delay(800)
println("job2 done")
}
job2.join()
job1.join() // 等待
println("测试结束")
}
运行log
[rustfisher]测试等待
job1 start
job2 start
job1 done
job2 done
测试结束
结构化的并发
用 GlobalScope.launch
时,会创建一个顶层协程。我们知道,它不使用主线程。新创的协程虽然轻量,但仍会消耗一些内存资源。如果忘记保持对新启动的协程的引用,它还会继续运行。
我们可以在代码中使用结构化并发。
示例中,我们使用 runBlocking
协程构建器将 main
函数转换为协程。在里面(作用域)启动的协程不需显式使用 join
。
观察下面的例子:
import kotlinx.coroutines.*
fun main() = runBlocking<Unit> {
println("主线程id ${Thread.currentThread().id}")
launch { // 在 runBlocking 作用域中启动一个新协程1
println("协程1所在线程id ${Thread.currentThread().id}")
delay(300)
println("协程1执行完毕")
}
launch { // 在 runBlocking 作用域中启动一个新协程2
println("协程2所在线程id ${Thread.currentThread().id}")
delay(500)
println("协程2执行完毕")
}
println("主线程执行完毕")
}
运行log
主线程id 1
主线程执行完毕
协程1所在线程id 1
协程2所在线程id 1
协程1执行完毕
协程2执行完毕
可以看到,不用像之前那样调用 Thread.sleep
或者 delay
让主线程等待一段时间,防止虚拟机退出。
程序会等待它所有的协程执行完毕,然后真正退出。
作用域构建器
使用 coroutineScope
构建器声明自己的作用域。它会创建一个协程作用域,并且会等待所有已启动子协程执行完毕。
runBlocking
与 coroutineScope
看起来类似,因为它们都会等待其协程体以及所有子协程结束。主要区别在于:
runBlocking
方法会阻塞当前线程来等待,是常规函数coroutineScope
只是挂起,会释放底层线程用于其他用途,是挂起函数
下面这个示例展示了作用域构建器的特点。main
是一个作用域。
import kotlinx.coroutines.*
fun main() = runBlocking { // this: CoroutineScope
launch {
delay(200L)
println("协程1 t${Thread.currentThread().id}")
}
coroutineScope { // 创建一个协程作用域
launch {
delay(500L)
println("内部协程2-1 t${Thread.currentThread().id}")
}
delay(100L)
println("协程2 t${Thread.currentThread().id}")
}
println("主任务完毕")
}
运行log
协程2 t1
协程1 t1
内部协程2-1t1
主任务完毕
提取函数重构
将 launch { …… }
内部的代码块提取到独立的函数中。提取出来的函数需要 suspend 修饰符,它是挂起函数。
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
fun main() = runBlocking<Unit> {
launch { r1() }
println("DONE")
}
// 挂起函数
suspend fun r1() {
delay(300)
println("[rustfisher] 提取出来的函数")
}
log
DONE
[rustfisher] 提取出来的函数
协程是轻量的
我们也试过,创建非常多的协程,程序运行OK。
下面的代码可以输出很多的点
import kotlinx.coroutines.*
fun main() = runBlocking {
for (t in 1..10000) {
launch {
delay(t * 500L)
print(".")
}
}
}
全局协程像守护线程
如果进程中只剩下了守护线程,那么虚拟机会退出。 前文那个打印 rustfisher.com
的例子,其实也能看到,字符没打印完程序就结束了。
在 GlobalScope 中启动的活动协程并不会使进程保活。它们就像守护线程。
再举一个例子
import kotlinx.coroutines.*
fun main() = runBlocking {
GlobalScope.launch {
for (i in 1..1000000) {
delay(200)
println("协程执行: $i")
}
}
delay(1000)
println("Bye~")
}
log
协程执行: 1
协程执行: 2
协程执行: 3
协程执行: 4
Bye~
Kotlin 入门教程指南
这里分享一份谷歌大佬推荐的《Kotlin 入门教程指南》,教程从第一章介绍 Kotlin各种特性开始,到第二章基础语法、习惯用法、编程规范,再到后面的基础、类与对象、函数与 Lambda 表达式、Java 互操作与 JavaScript、协程、工具 等等, 结合实例对 Kotlin进行详细的讲解。
一,概述
- 使用 Kotlin 进行服务器端开发
- 使用 Kotlin 进行 Android 开发
- Kotlin JavaScript 概述
- Kotlin/Native 用于原生开发
- 用于异步编程等场景的协程
- Kotlin 1.1 的新特性
- Kotlin 1.2 的新特性
- Kotlin 1.3 的新特性
二,开始
- 基本语法
- 习惯用法
- 编码规范
三,基础
- 基本类型
- 包
- 控制流:if、when、for、while
- 返回和跳转
四,类与对象
- 类与继承
- 属性与字段
- 接口
- 可见性修饰符
- 扩展
- 数据类
- 密封类
- 泛型
- 嵌套类与内部类
- 10 枚举类
- 对象表达式与对象声明
- Inline classes
- 委托
- 委托属性
五,函数与 Lambda 表达式
- 函数
- 高阶函数与 lambda 表达式
- 内联函数
六,其他
- 解构声明
- 集合:List、Set、Map
- 区间
- 类型的检查与转换“is”与“as”
- This 表达式
- 相等性
- 操作符重载
- 空安全
- 异常
- 注解
七,Java 互操作与 JavaScript
- 在 Kotlin 中调用 Java 代码
- Java 中调用 Kotlin
- JavaScript 动态类型
- Kotlin 中调用 JavaScript
- JavaScript 中调用 Kotlin
- JavaScript 模块
- JavaScript 反射
- JavaScript DCE
八,协程
- 协程基础
- 取消与超时
- 通道 (实验性的)
- 组合挂起函数
- 协程上下文与调度器
- 异常处理
- select 表达式(实验性的)
- 共享的可变状态与并发
九,工具
- 编写 Kotlin 代码文档
- Kotlin 注解处理
- 使用 Gradle
- 使用 Maven
- 使用 Ant
- Kotlin 与 OSGi
- 编译器插件
- 不同组件的稳定性
需要完整版《Kotlin 入门教程指南》PDF资料 可以*********