协程初识
协程定义
官方定义为轻量级的线程. 传统线程在运行时,具有运行时数据,当发生线程切换时,需要将线程的栈数据和指令计数器等信息保存到进程的pcb中,发生用户态跟内核态的切换.一般情况下,一次切换的耗时大约在3.5us左右.
协程可以理解为纯用户态的线程,其通过协作而不是抢占来进行切换。相对于进程或者线程,协程所有的操作都可以在用户态完成,创建和切换的消耗更低。
协程为协同任务提供了一种运行时抽象,这种抽象非常适合于协同多任务调度和数据流处理。因为用户态线程切换代价比内核态线程小,协程成为了一种轻量级的多任务模型
hello world
fun main() {
var job = GlobalScope.launch {
delay(1000)
println("world")
println(Thread.currentThread().name)
}
print("hello,")
Thread.sleep(2000)
}
//输出结果为 hello,world
DefaultDispatcher-worker-1
从输出可以看出
- GlobalScope.launch开了一个名为DefaultDispatcher-worker-x的线程.
- GlobalScope.launch启动了一个作用域为GlobalScope的Job,并将Job加入到了作用域为GlobalScope的任务队列,并挂起1000ms.
- 由于主线程执行完毕后会终止程序,因为最后sleep(2000),让协程Job得以执行.
关于delay和sleep
- delay是协程中专有的方法,将协程挂起,并不释放cpu资源.协程所在的线程会去继续查找并执行其它的任务(Job).
- Thread.sleep()将线程暂停执行,进入(TIMED_WAITING)等待状态,线程在sleep过程中不会释放它已经获得的任意的monitor和lock等资源,但是会释放cpu资源(调度算法在睡眠时间内不会调度到该线程).睡眠结束以后,并不是直接回到运行态,而是进入就绪队列,要等到其他进程放弃时间片后才能重新进入运行态
runBlocking
- runBlocking将一个线程包装成协程,内部实现会将线程传递给BlockingCoroutine
- 它的默认作用域是CoroutineScope
- job.join作用与thread.join类似,等待任务执行结果.
fun main() = runBlocking {
val job = GlobalScope.launch {
delay(1000)
println("world")
}
print("hello,")
job.join() // 等待子协程执行结束
println("all coroutine done !")
}
//输出
hello,world
all coroutine done !
默认作用域CoroutineScope
- lauch启动的job加入到了CoroutineScope所在的协程的任务队列
- 由于协程任务没有执行完成,因此主线程不会结束,后面的all coroutine done会先打印
fun main() = runBlocking {
val job = launch {
delay(1000)
println("world")
}
print("hello,")
println("all coroutine done !")
}
//输出
hello,all coroutine done !
world
四个场景看协程
场景一
fun main() = runBlocking {
var job = launch {
println("world")
}
print("hello,")
}
// 输出为
hello,world
- launch启动了一个job,加入到CoroutineScope作用域下
- 先执行
print("hello,")
,然后执行job
场景二
fun main() = runBlocking {
var job = launch {
delay(300)
println("task from runblocking ")
}
coroutineScope {
var jobChild = launch {
delay(400)
println("task from nested scope")
}
}
println("end of blocking")
}
//输出
task from runblocking
task from nested scope
end of blocking
- launch启动了一个job,加入到CoroutineScope作用域下
- coroutineScope它会创建一个协程作用域,并且在所有已启动子协程执行完毕之前不会结束
- runBlocking要等所有的子作用域的执行结束才结束
场景三
fun main() = runBlocking {
var job = launch {
delay(300)
println("task from runblocking ")
}
println("end of blocking")
}
//输出
end of blocking
task from runblocking
- launch启动了一个job,加入到CoroutineScope作用域下,并进入挂起状态
- runBlocking后续代码先执行
场景四 coroutineScope方法
fun main() = runBlocking {
var job = launch {
delay(300)
println("task from runblocking ")
}
coroutineScope {
var jobChild = launch {
delay(300)
println("task from nested scope")
}
}
println("end of blocking")
}
//输出
task from runblocking
task from nested scope
end of blocking
- launch启动了一个job,加入到CoroutineScope作用域下
- coroutineScope它会创建一个协程作用域,并且在所有已启动子协程执行完毕之前不会结束.由于存在coroutineScope,因此后面的println("end of blocking")不会先执行.
可挂起函数 suspend/delay
- 可挂起函数由suspend修饰,它必须在协程内部调用.
- suspend 内部可以调用delay方法,如果没有调用delay,不需要申明为suspend
fun main() = runBlocking {
launch {
testDelay()
}
}
suspend fun testDelay(){
delay(1000)
println("testDelay")
}
协程轻量实验
通过打印可以发现,开启了10000个协程,实际上使用的是同一个线程main.
fun main() = runBlocking {
repeat(10000){
launch {
delay(1000)
print(". ${Thread.currentThread().name}")
}
}
}
协程取消执行 delay/cancel
- 当任务处于挂起状态时(有调用delay),取消才能够成功.
- 如果没有处于挂起状态,需要增加isActive判断是否取消
- 当协程被cancel后,会抛出JobCancellationException,它被认为是协程正常结束的原因.可以通过try/catch捕获,也可以不捕获.
非挂起函数无法取消
fun main() = runBlocking {
var startTime = System.currentTimeMillis()
var job = launch {
var i = 0
var nextTime = startTime
while (i < 5){
if (System.currentTimeMillis() >= nextTime){
println("----calc---- ${i++}")
nextTime += 500
}
}
}
delay(1500)
job.cancel()
println("end")
}
//输出是
----calc---- 0
----calc---- 1
----calc---- 2
----calc---- 3
----calc---- 4
end
挂起函数可以取消
fun main() = runBlocking {
var startTime = System.currentTimeMillis()
var job = launch {
var i = 0
var nextTime = startTime
while (i < 5){
delay(500)
println("----calc---- ${i++}")
}
}
delay(1500)
job.cancel()
println("end")
}
//输出
----calc---- 0
----calc---- 1
end
其它特殊取消
- launch 使用Dispatchers.Default 这个context
- 通过关键字isActive判断
fun main() = runBlocking {
var startTime = System.currentTimeMillis()
var job = launch(Dispatchers.Default) {
var i = 0
var nextTime = startTime
while (isActive){
if (System.currentTimeMillis() >= nextTime){
println("----calc---- ${i++}")
nextTime += 1000
}
}
}
delay(1500)
job.cancelAndJoin()
println("end")
}