Kotlin杂谈系列十(浅谈)
-
先来说说并发和并行吧
并发 : 打个例子吧 就是就和边吃东西和边讲话 不可能同时在吃东西的时候在流利的讲话 所以这就是并发
并行 : 就好比你可以边说话和边走路 这就是并行
至于底层的内容我不敢乱说所以点到为止
-
协程也是函数 只是他能记录函数的状态 他不必一般的函数只有一个入口,而协程有多个入口协程可以在函数的中间部分执行 也可以先执行一部分然后然其他任务来执行在执行其他的任务 协程是可以记录调用状态的
-
协程是应用层面的不是计算机底层的 用到了一种**数据结构(continuations)**来保留状态信息
-
可以这样理解 在一个线程中有多个任务 比如有一个耗时的任务 先执行他等待数据的响应 自己去执行其他的任务当数据响应回来了再去执行这个耗时的任务的其他部分 这样可以避免线程阻塞 如果是以前的话耗时的任务只能在不同的线程去执行(这是我的理解不知道对不对毕竟我对进程阻塞和进程和线程在计算机中不是很了解)
-
现在来看看kotlin对协程的支持
首先第一步 引用依赖
dependencies { implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.0-RC' }
-
然后再能用kotlin提供的函数和一些支持API
-
runBlocking 自带协程上下文(我的理解是一个线程)
-
launch 启动一个新协程来执行lambda 他会返回一个Job(作业类) 这个的作用是等待任务完成或者取消任务
fun task1() {
println("start task1 ${Thread.currentThread()}")
println("end task1 ${Thread.currentThread()}")
}
fun task2() {
println("start task2 ${Thread.currentThread()}")
println("end task2 ${Thread.currentThread()}")
}
fun main() {
runBlocking {
launch { task2() }
println("task1 task2 ${Thread.currentThread()}")
}
println("done")
}
-
runBlocking中的lambda会在一个协程里执行
-
launch中的lambda会在一个新的协程里执行
-
使用两个函数将函数挂起
delay() 和 yield()
-
delay() 将当前执行的任务暂停指定的毫秒数
-
yield() 将当前任务挂起不会导致任何显式延迟
但是他们都为另一个挂起的任务提供了执行的机会
-
-
注意 : 这两个函数 不是任何一个函数都可以调用的 必须是用suspend修饰的函数才能调用
suspend fun task1() {
println("start task1 ${Thread.currentThread()}")
yield()
delay(4000)
println("end task1 ${Thread.currentThread()}")
}
- launch() 和 runBlocking 函数的调用导致协程在与调用方的协程作用域相同的线程中执行
fun main() {
runBlocking {
launch { task2() }
println("task1 task2 ${Thread.currentThread()}")
}
println("done")
}
//输出 :
task1 task2 Thread[main,5,main]
start task2 Thread[main,5,main]
end task2 Thread[main,5,main]
done
-
调用方协程作用域就是runBlocking的大括号里面 而 launch里面的协程就和runBlocking的协程所在同一个线程
-
通俗一点 协程的上下文就是这个协程所在的线程
-
可以显式为launch() 和 runBloking()的设置上下文(线程) 只需要传递一个CoroutineContext
-
kotlin库中有几个默认的CoroutineContext
-
Dispatchers.Default : 表示在Default-Dispatcher池的线程中开始执行协程 这个池用于运行计算密集型的任务
-
Dispatchers.IO : 专门用于运行IO密集型任务 如果线程在IO中阻塞,并且创建了更多的任务 那么这个池的大小可能会变
-
Dispatcher.Main : 主要用于Android设备和Swing UI 来运行只从main线程中更新UI的任务
fun main() {
runBlocking(Dispatchers.IO) {
launch (Dispatchers.Default){ task2() }
println("task1 task2 ${Thread.currentThread()}")
}
println("done")
}
//输出
task1 task2 Thread[DefaultDispatcher-worker-1,5,main]
start task2 Thread[DefaultDispatcher-worker-3,5,main]
end task2 Thread[DefaultDispatcher-worker-3,5,main]
done
-
协程的并发执行是在一个线程中 而协程的并行执行是在多个线程中
-
要是java的话 似乎并行操作是多核的事 而并发操作是多线程的事
-
自定义线程池运行
- 设置单线程池上下文 : 我们先要创建一个单线程执行器 然后调用kotlin的扩展函数 **asCoroutineDispatcher()**函数来得到一个CoroutineContext(上下文)
- 熟悉java的朋友应该知道我们不关闭执行器的话程序永远不会停止 因为执行器池中还有一个活动线程 他将使JVM保持活动状态
- 但是我们可以使用kotlin 为我们封装好了的use()函数他能知道何时协程接收然后帮我们关闭执行器
fun main() { Executors.newSingleThreadExecutor().asCoroutineDispatcher().use { runBlocking { launch { task2() } println("task1 task2 ${Thread.currentThread()}") } println("done") } }
- Executors.newSingleThreadExecutor().asCoroutineDispatcher() 当一个单线程执行器包装成了一个协程调度器
-
在挂起后切换线程
这个的利用到CoroutineContext 以及另一个参数CoroutineStart 来实现这一点
看看源码
public fun CoroutineScope.launch( context: CoroutineContext = EmptyCoroutineContext, start: CoroutineStart = CoroutineStart.DEFAULT,/*这里*/ block: suspend CoroutineScope.() -> Unit ): Job { val newContext = newCoroutineContext(context) val coroutine = if (start.isLazy) LazyStandaloneCoroutine(newContext, block) else StandaloneCoroutine(newContext, active = true) coroutine.start(start, coroutine, block) return coroutine }
- CoroutineStart.DEFAULT : 表示使用LAZY来延迟执行直到显示的调用start()
- CoroutineStart.ATOMIC : 表示以不可取消的模式来运行
- CoroutineStart.UNDISPATCHED : 表示在最初的上下文运行 但在挂起点之后切换线程
suspend fun task2() {
println("start task2 ${Thread.currentThread()}")
yield()
println("end task2 ${Thread.currentThread()}")
}
fun main() {
Executors.newSingleThreadExecutor().asCoroutineDispatcher().use {context ->
runBlocking {
launch(context = context, start = CoroutineStart.UNDISPATCHED) { task2() }
println("task1 task2 ${Thread.currentThread()}")
}
println("done")
}
}
//输出:
start task2 Thread[main,5,main]//注意这里
task1 task2 Thread[main,5,main]
end task2 Thread[pool-1-thread-1,5,main]//注意这里
done
-
在挂起前他是在 Thread[main,5,main] 在挂起后他是在 Thread[pool-1-thread-1,5,main]
-
(context = context, start = CoroutineStart.UNDISPATCHED) 这两个参数的意思是 启动方式是CoroutineStart.UNDISPATCHED (挂起后切换协程上下文–(线程)) context = context 这个决定了切换到那个线程去
-
launch 和 runBlocking 的默认协程上下文都是main
//launch的函数头
public fun CoroutineScope.launch(
context: CoroutineContext = EmptyCoroutineContext/*默认是个空的*/,
start: CoroutineStart = CoroutineStart.DEFAULT,
block: suspend CoroutineScope.() -> Unit
)
//runBlocking的函数头
public actual fun <T> runBlocking(context: CoroutineContext, block: suspend CoroutineScope.() -> T)
// actual这个关键字表示多平台 所以这个我就不是很理解 但是通过调试发现他默认也是主线程(main)
-
在一个上下文的协程执行是中途切换其他协程上下文
要用一个另一个函数 **withContext() **这个函数可以只让一部分代码在另一个协程中运行,其余代码还是在原来的上下文中运行
fun main() {
Executors.newSingleThreadExecutor().asCoroutineDispatcher().use {context ->
runBlocking {
withContext(Dispatchers.Default){ task1() }/*在这里*/
println("task1 task2 ${Thread.currentThread()}")
}
println("done")
}
}
-
withContext(Dispatchers.Default){ task1() }就只有这段代码在Dispatchers.Default(上下文)中执行 其他的代码还是在context中执行
-
可以返回协程的执行结果的两个函数
async() 和 await() 这两个函数配合使用就可以得到在协程中返回值
还记得launch函数吗 他返回一个Job对象 该对象只能用于等待协程的终止和取消但是async()返回一个未来的对象(协程中的返回值结果就包装在里面)
然后调用await()函数就可以得到使用async()启动协程的结果
fun main() {
runBlocking {
val count : Deferred<Int> /*这里的泛型指定的就是返回值的类型*/ = async{/*这里*/
println("fecthing in ${Thread.currentThread()}")
Runtime.getRuntime().availableProcessors()//返回的也必须是泛型指定的类型
}
println("Called the function is ${Thread.currentThread()}")
println("Number of cores is ${count.await()}, ${count.await().javaClass}")
}
}
//输出 :
Called the function is Thread[main,5,main]
fecthing in Thread[main,5,main]
Number of cores is 8, class java.lang.Integer
- 底层的 源码有点小高深看看吧
public fun <T> CoroutineScope.async(
context: CoroutineContext = EmptyCoroutineContext,
start: CoroutineStart = CoroutineStart.DEFAULT,
block: suspend CoroutineScope.() -> T
): Deferred<T> /*这就是返回值类型*/ {
val newContext = newCoroutineContext(context)
val coroutine = if (start.isLazy)
LazyDeferredCoroutine(newContext, block) else
DeferredCoroutine<T>(newContext, active = true)
coroutine.start(start, coroutine, block)//这里应该将
return coroutine
}
-
我只能看懂这些了
-
目前就记住 async中lambda表达式的返回值存放在async返回的对象里
-
协程的底层是用数据结构(延续)实现的
说的不严谨点 可以把延续看成闭包 把挂起点之后的代码变成一个闭包 需要的时候就调用这个闭包
- 使用协程创建无限序列
//得到一个无限序列
fun primes(start : Int):Sequence<Int> = sequence {
println("starting to look")
var index = start
while (true){
if (index >1 && (2 until index).none { i-> index % i==0 }){
yield(index)/*返回值: 序列中的元素*/
println("Generating next after $index")
}
index++
}
}
fun main() = runBlocking<Unit> {
for(prime in primes(start = 17)){
println("Received $prime")
if (prime >= 30) break
}
}
- 通过协程创建无限的集合
operator fun ClosedRange<String>.iterator() : Iterator<String>/*这个泛型指定的就是yield返回的值*/ = iterator {
val next = StringBuffer(start)
val last = endInclusive
while(last >= next.toString() && last.length >= next.length){
val result = next.toString()
val lastChar = next.last()
if (lastChar < Char.MAX_VALUE){
next.setCharAt(next.length-1,lastChar+1)
}else{
next.append(Char.MIN_VALUE)
}
yield(result)/*这里把值返回了*/
}
}
fun main() = runBlocking<Unit> {
for(world in "a".."z"){
println("Received $world")
}
}
- 这次杂谈主要是浅浅的谈了一下协程