阻塞与非阻塞

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 构建器声明自己的作用域。它会创建一个协程作用域,并且会等待所有已启动子协程执行完毕。

runBlockingcoroutineScope 看起来类似,因为它们都会等待其协程体以及所有子协程结束。主要区别在于:

  • 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资料 可以*********