协程最早诞生于 1958 年,被应用于汇编语言中(距今已有 60 多年了),对它的完整定义发表于 1963 年,协程是一种通过代码执行的恢复与暂停来实现协作式的多任务的程序组件。而与此同时,线程的出现则要晚一些,伴随着操作系统的出现,线程大概在 1967 年被提出。
OpenJDK – Loom:其中引入了轻量级和高效的虚拟线程,而且是有栈协程
在 JVM 平台上,Kotlin 协程是无栈协程,所谓无栈协程,是指协程在 suspend 状态时不需要保存调用栈。
Hello World
fun main() {
GlobalScope.launch {
delay(1000)
println("Kotlin Coroutine: ${Thread.currentThread().name}")
}
println("Hello: ${Thread.currentThread().name}")
Thread.sleep(2000)
println("World: ${Thread.currentThread().name}")
}
执行结果:
Hello: main
Kotlin Coroutine: DefaultDispatcher-worker-1
World: main
代码解释:
如果不调用 Thread.sleep,那么 launch 里的代码不会执行,程序就退出了
至于为什么,先不做解释,读到后面自然就找到答案了
runBlocking
fun main() = runBlocking {
GlobalScope.launch {
delay(1000)
println("Kotlin Coroutines")
}
println("Hello")
delay(2000)
println("World")
}
执行结果:
Hello
Kotlin Coroutines
World
runBlocking 的官方 doc:
运行新的协程并中断阻塞当前线程,直到其完成
不应从协程使用此函数
桥接协程代码和非协程代码,通常用在 main 函数和单元测试
runBlocking 的思考
fun main() = runBlocking {
GlobalScope.launch {
delay(2000)
println("Kotlin Coroutines")
}
println("Hello")
delay(1000)
println("World")
}
执行结果:
Hello
World
原因分析:
GlobalScope 的 launch 函数返回一个 Job 对象
先来阅读一下 Job 的官方 doc
核心作业接口 – 后台作业
后台作业是一个可以取消的东西,其生命周期最终以 completion 为终点
后台作业可以被安排到父子层次结构中,其中取消父级会导致立即递归取消其 children 所有作业。
另外,产生异常的子协程的失败(CancellationException 除外),将立即取消其父协程,从而取消其所有其他子协程。
Job 的最基本的实例接口是这样创建的:
Job 接口及其所有派生接口对于第三方库中的继承并不稳定,因为将来可能会向此接口添加新方法,但可以使用稳定。
协程作用域 - GlobalScope
// runBlocking 作用域
fun main() = runBlocking {
// GlobalScope.launch 作用域
val job: Job = GlobalScope.launch {
delay(1000)
println("Kotlin Coroutines")
}
println("Hello")
// 同一作用域下, 所有启动的协程全部完成后才会完成
// 但是, 此处是不同的作用域, 所以一定要 join
job.join()
println("World")
}
执行结果:
Hello
Kotlin Coroutines
World
fun main() = runBlocking {
launch {
delay(1000)
println("Kotlin Coroutines")
}
println("Hello")
}
执行结果:
Hello
Kotlin Coroutines
coroutineScope 函数
fun main() = runBlocking {
println(Thread.currentThread().name)
launch {
delay(1000)
println("my job1 -> ${Thread.currentThread().name}")
}
println("person -> ${Thread.currentThread().name}")
coroutineScope {
launch {
delay(10 * 1000)
println("my job2 -> ${Thread.currentThread().name}")
}
delay(5 * 1000)
println("hello world -> ${Thread.currentThread().name}")
}
launch {
println("block? -> ${Thread.currentThread().name}")
}
println("welcome -> ${Thread.currentThread().name}")
}
执行结果:
main
person -> main
my job1 -> main
hello world -> main
my job2 -> main
welcome -> main
block? -> main
协程的取消
fun main() = runBlocking {
val job = GlobalScope.launch {
repeat(200) {
println("hello: $it")
delay(500)
}
}
delay(1100)
println("Hello World")
// job.cancel()
// job.join()
job.cancelAndJoin()
println("welcome")
}
通过 cancelAndJoin 是 cancel 和 join 两个函数的结合
执行 job.cancelAndJoin(),执行结果为:
hello: 0
hello: 1
hello: 2
Hello World
welcome
执行 job.cancel(), 执行结果为:
hello: 0
hello: 1
hello: 2
Hello World
welcome
#执行结果和上述一致,虽然是不同的作用域
执行 job.join(), 执行结果为:
hello: 0
hello: 1
hello: 2
Hello World
hello: 3
hello: 4
hello: 5
···
hello: 199
fun main() = runBlocking {
val startTime = System.currentTimeMillis()
val job = launch(Dispatchers.Default) {
var nextPrintTime = startTime
var i = 0
while (i < 20) { // 此处就是处于计算过程中,而且没有检查取消状态,无法取消
if (System.currentTimeMillis() >= nextPrintTime) {
println("job: I am sleeping ${i++}")
nextPrintTime += 500L
}
}
}
delay(1300)
println("hello wworld")
job.cancelAndJoin()
println("welcome")
}
执行结果:
job: I am sleeping 0
job: I am sleeping 1
job: I am sleeping 2
hello wworld
job: I am sleeping 3
···
job: I am sleeping 19
welcome
fun main() = runBlocking {
val startTime = System.currentTimeMillis()
val job = launch(Dispatchers.Default) {
var nextPrintTime = startTime
var i = 0
/*while (i < 20) { // 方式1
if (System.currentTimeMillis() >= nextPrintTime) {
println("job: I am sleeping ${i++}")
nextPrintTime += 500L
}
yield()
}*/
while (isActive) { // 方式2
if (System.currentTimeMillis() >= nextPrintTime) {
println("job: I am sleeping ${i++}")
nextPrintTime += 500L
}
}
}
delay(1300)
println("hello wworld")
job.cancelAndJoin()
println("welcome")
}
以上两种方式的执行结果:
job: I am sleeping 0
job: I am sleeping 1
job: I am sleeping 2
hello wworld
welcome
使用 finally 来关闭资源
fun main() = runBlocking {
val job = launch {
try {
repeat(100) {
println("job repeat $it")
delay(500)
}
} catch (e: Exception) {
e.printStackTrace()
} finally {
println("execute finally")
}
}
delay(1300)
println("hello")
job.cancelAndJoin()
println("world")
}
输出结果:
job repeat 0
job repeat 1
job repeat 2
hello
execute finally
world
kotlinx.coroutines.JobCancellationException: StandaloneCoroutine was cancelled; job=StandaloneCoroutine{Cancelling}@4f8e5cde
fun main() {
test()
//testNonCancellable()
}
fun test() = runBlocking {
val job = launch {
try {
repeat(100) {
println("job repeat $it")
delay(500)
}
} catch (e: Exception) {
e.printStackTrace()
} finally {
println("execute finally")
delay(1000)
println("after delay 1000")
}
}
delay(1300)
println("hello")
job.cancelAndJoin()
println("world")
}
执行结果
job repeat 0
job repeat 1
job repeat 2
hello
execute finally
world
kotlinx.coroutines.JobCancellationException: StandaloneCoroutine was cancelled; job=StandaloneCoroutine{Cancelling}@4f8e5cde
fun testNonCancellable() = runBlocking {
val job = launch {
try {
repeat(100) {
println("job repeat $it")
delay(500)
}
} finally {
withContext(NonCancellable) {
println("execute finally")
delay(1000)
println("after delay 1000")
}
}
}
delay(1300)
println("hello")
job.cancelAndJoin()
println("world")
}
执行结果
job repeat 0
job repeat 1
job repeat 2
hello
execute finally
after delay 1000
world
函数组合
fun main() = runBlocking {
val elapsedTime = measureTimeMillis {
val value1 = intValue1()
val value2 = intValue2()
println("$value1 + $value2 = ${value1 + value2}")
}
println("total time: $elapsedTime")
}
private suspend fun intValue1(): Int {
delay(2000)
return 15
}
private suspend fun intValue2(): Int {
delay(3000)
return 20
}
输出结果:
15 + 20 = 35
total time: 5010
async 和 await
fun main() = runBlocking {
val elapsedTime = measureTimeMillis {
val s1 = async { intValue1() }
val s2 = async { intValue2() }
val value1 = s1.await()
val value2 = s2.await()
println("$value1 + $value2 = ${value1 + value2}")
}
println("total time: $elapsedTime")
}
private suspend fun intValue1(): Int {
delay(2000)
return 15
}
private suspend fun intValue2(): Int {
delay(3000)
return 20
}
执行结果:
15 + 20 = 35
total time: 3018
和 Job 一样,Deferred 如果启动参数设置为 CoroutineStart.LAZY,那么同样需要先激活,Deferred 比 Job 多了一个可以激活状态的方法:await
fun main() = runBlocking {
val elapsedTime = measureTimeMillis {
val s1 = async(start = CoroutineStart.LAZY) {
intValue1()
}
val s2 = async(start = CoroutineStart.LAZY) {
intValue2()
}
println("hello world")
Thread.sleep(2500)
delay(2500)
val value1 = s1.await()
val value2 = s2.await()
println(value1 + value2)
}
println("total time: $elapsedTime")
}
private suspend fun intValue1(): Int {
delay(2000)
return 15
}
private suspend fun intValue2(): Int {
delay(3000)
return 20
}
执行结果
hello world
35
total time: 10032
结构化并发程序开发
fun main() = runBlocking {
val elapsedTime = measureTimeMillis {
println("intSum: ${intSum()}")
}
println("total time: $elapsedTime")
}
private suspend fun intSum(): Int = coroutineScope<Int> {
val s1 = async { intValue1() }
val s2 = async { intValue2() }
s1.await() + s2.await()
}
private suspend fun intValue1(): Int {
delay(2000)
return 15
}
private suspend fun intValue2(): Int {
delay(3000)
return 20
}
执行结果
intSum: 35
total time: 3016
分发器
fun main() = runBlocking<Unit> {
launch {
println("no param, thread: ${Thread.currentThread().name}")
}
launch(Dispatchers.Unconfined) {
println("dispatchers unconfined, thread: ${Thread.currentThread().name}")
delay(100) // 加上延迟,就会发现不是运行在main线程了
println("dispatchers unconfined, thread: ${Thread.currentThread().name}")
}
launch(Dispatchers.Default) {
println("dispachers default, thread: ${Thread.currentThread().name}")
}
val dispatcher = Executors.newSingleThreadExecutor().asCoroutineDispatcher()
launch(dispatcher) {
println("single thread executor service, thread: ${Thread.currentThread().name}")
dispatcher.close()
}
GlobalScope.launch {
println("globle scope launch, thread: ${Thread.currentThread().name}")
}
}
执行结果:
dispatchers unconfined, thread: main
dispachers default, thread: DefaultDispatcher-worker-1
single thread executor service, thread: pool-1-thread-1
globle scope launch, thread: DefaultDispatcher-worker-1
no param, thread: main
dispatchers unconfined, thread: kotlinx.coroutines.DefaultExecutor
fun main() = runBlocking<Unit> {
val singleDispatcher = ThreadPoolExecutor(
1,
1,
0,
TimeUnit.SECONDS,
ArrayBlockingQueue(1)
).asCoroutineDispatcher()
repeat(10) {
launch(singleDispatcher) {
try {
delay(100)
println("${Thread.currentThread().name} - $it")
} catch (e: Exception) {
println(e.message)
} finally {
println("${Thread.currentThread().name} - finally")
}
}
}
}
执行结果:
pool-1-thread-1 - 0
pool-1-thread-1 - finally
The task was rejected
DefaultDispatcher-worker-1 - finally
pool-1-thread-1 - 3
pool-1-thread-1 - finally
The task was rejected
DefaultDispatcher-worker-1 - finally
pool-1-thread-1 - 6
pool-1-thread-1 - finally
The task was rejected
DefaultDispatcher-worker-1 - finally
pool-1-thread-1 - 9
pool-1-thread-1 - finally
调试
private fun log(msg: String) {
println("[${Thread.currentThread().name}] - $msg")
}
fun main() = runBlocking {
val s1 = async {
delay(2000)
log("hello world")
10
}
val s2 = async {
log("welcome")
20
}
log("The result is ${s1.await() + s2.await()}")
}
执行结果:
[main @coroutine#3] - welcome
[main @coroutine#2] - hello world
[main @coroutine#1] - The result is 30
/
private fun log(msg: String) {
println("[${Thread.currentThread().name}] - $msg")
}
fun main() {
newSingleThreadContext("Context1").use { ctx1 ->
newSingleThreadContext("Context2").use { ctx2 ->
runBlocking(ctx1) {
log("started in context1")
withContext(ctx2) {
log("working in context2")
}
log("Back to context1")
}
}
}
}
执行结果:
[Context1 @coroutine#1] - started in context1
[Context2 @coroutine#1] - working in context2
[Context1 @coroutine#1] - Back to context1
通过查看 CoroutineContext 的继承关系和源码得知,每一个 Element 都有一个名为 Key 的半生对象
fun main() = runBlocking {
val job: Job? = coroutineContext[Job]
println(job)
println(coroutineContext.isActive)
}
执行结果
BlockingCoroutine{Active}@4459eb14
true
ThreadLocal
asContextElement 扩展方法可以转换成 CoroutineContext 对象
val threadLocal = ThreadLocal<String>()
private fun printCurrentThreadValue(info: String) {
println("$info, ${Thread.currentThread().name}, local value is: ${threadLocal.get()}")
}
fun main() = runBlocking {
val newDispatcher = Executors.newSingleThreadExecutor().asCoroutineDispatcher()
threadLocal.set("Main Value")
printCurrentThreadValue("start main")
val job = launch(Dispatchers.Default + threadLocal.asContextElement("Coroutine Value")) {
printCurrentThreadValue("launch a coroutine")
yield()
printCurrentThreadValue("coroutine after yield")
launch {
printCurrentThreadValue("run in child")
}
launch(newDispatcher) {
printCurrentThreadValue("run in new thread")
}
}
val job2 = launch(Dispatchers.Default) {
printCurrentThreadValue("run in other coroutine")
}
val job3 = launch {
printCurrentThreadValue("run in other2 coroutine")
}
job.join()
job2.join()
job3.join()
newDispatcher.close()
printCurrentThreadValue("end main")
}
执行结果:
start main, main, local value is: Main Value
launch a coroutine, DefaultDispatcher-worker-1, local value is: Coroutine Value
coroutine after yield, DefaultDispatcher-worker-1, local value is: Coroutine Value
run in child, DefaultDispatcher-worker-2, local value is: Coroutine Value
run in other coroutine, DefaultDispatcher-worker-2, local value is: null
run in new thread, pool-1-thread-1, local value is: Coroutine Value
run in other2 coroutine, main, local value is: Main Value
end main, main, local value is: Main Value
可以发现 ThreadLocal 通过使用 asContextElement 方法,协程之间共享数据遵循协程作用域的范围。
https://github.com/JetBrains/kotlin
「三翼鸟数字化技术平台-智家APP平台」 通过持续迭代演进移动端一站式接入平台为三翼鸟APP、智家APP等多个APP提供基础运行框架、系统通用能力API、日志、网络访问、页面路由、动态化框架、UI组件库等移动端开发通用基础设施;通过Z·ONE平台为三翼鸟子领域提供项目管理和技术实践支撑能力,完成从代码托管、CI/CD系统、业务发布、线上实时监控等Devops与工程效能基础设施搭建。