协程的挂起与线程的休眠
协程通过delay(timeMillis)实现挂起,线程通过sleep(timeMillis)实现休眠。但是挂起和休眠存着差异性
协程挂起与线程休眠
相同点:
1.都能达到堵塞的目的;
2.在该状态下(挂起/休眠)都能被终止执行(取消/中断);
3.取消/中断时都会抛出异常
不同点:
1.线程休眠会直接堵塞当前线程,该线程无法再执行其它操作,但是协程挂起不会堵塞当前线程,线程上的其它协程可以继续运行;
2.delay操作只能在协程环境使用,sleep在协程环境和普通线程环境都可使用
3.线程中断抛出的是InterruptedException异常,协程中断抛出的是CancellationException异常
协程的取消与线程的中断
协程的取消与线程的中断,2者十分类似 :
比较 | 调用api | 能被直接取消的状态 | 事务处理时 | 抛出的异常 |
---|---|---|---|---|
协程 | cancel() | delay | 无法被直接取消 | CancellationException |
线程 | interrupt() | sleep | 无法被直接取消 | InterruptedException |
相同点:
1.都是需要处于挂起/休眠状态,才能够直接取消/中断;
2.处于事务处理时,无法直接被取消/中断;
不同点在于:
1.协程存在父协程的概念,但是线程没有啥所谓的父线程。取消父协程后,会自动取消其所有的子协程;
2.协程在挂起时被取消,会抛出CancellationException异常,线程在休眠时被中断,会抛出InterruptedException
示例
能被直接取消/中断
该协程job在执行cancel的时候,处于delay状态,能够被直接取消
fun cancel() = runBlocking {
val job = launch {
println("1")
delay(3000)
//被cancel后,下边不再执行
println("2")
}
delay(100)
job.cancel()
println("3")
}
打印出:
1
3
该线程thread在执行interrupt的时候,处于sleep状态,能够被直接中断
fun threadInterrupt() {
val thread = thread {
println("1")
Thread.sleep(1 * 1000)
println("2")
}
//抛出InterruptedException但不影响后面执行
thread.interrupt()
Thread.sleep(3 * 1000)
println("3")
}
打印出:
1
3
处于事务处理,无法被直接取消/中断
协程在执行cancel()后,内部的扩展属性isActive会置为false,代码中可以结合该变量跳出事务处理的流程。 如下例子中的while()判断,假设没有结合iaActive,因为当前协程处于事务处理,不是delay挂起状态,无法直接被cancel, 需要等待事务处理完毕。
为了达到cancel后尽早结束协程,可以结合isActive进行判断。
fun cancel() = runBlocking {
val job = launch(Dispatchers.Default) {
var nextPrintTime = System.currentTimeMillis()
var i = 0
//isActive是协程扩展属性,cancel()后变为false
while (i < 5 && isActive) {
// 一个执行计算的循环,只是为了占用 CPU
if (System.currentTimeMillis() >= nextPrintTime) {
println("job: I'm sleeping ${i++} ...")
// 每秒打印消息两次
nextPrintTime += 500L
}
}
}
delay(100)
job.cancel()
println("end")
}
结合isActive进行中断判断,打印出:
job: I'm sleeping 0 ...
end
假设没有结合isActive进行判断,打印出:
job: I'm sleeping 0 ...
end
job: I'm sleeping 1 ...
job: I'm sleeping 2 ...
job: I'm sleeping 3 ...
job: I'm sleeping 4 ...
线程使用interrupt()结合isInterrupted变量,达到终端操作的目的,效果与分析同协程cancel()结合isActive类似:
fun threadInterrupt() {
val thread = thread {
var nextPrintTime = System.currentTimeMillis()
var i = 0
//isInterrupted是线程属性,interrupt()后变为false
while (i < 5 && !Thread.currentThread().isInterrupted) {
// 一个执行计算的循环,只是为了占用 CPU
if (System.currentTimeMillis() >= nextPrintTime) {
println("thread: I'm sleeping ${i++} ...")
// 每秒打印消息两次
nextPrintTime += 500L
}
}
}
thread.interrupt()
Thread.sleep(3 * 1000)
println("end")
}
结合isInterrupted进行中断判断,打印出:
thread: I'm sleeping 0 ...
end
假设没有结合isInterrupted进行判断,打印出:
thread: I'm sleeping 0 ...
end
thread: I'm sleeping 1 ...
thread: I'm sleeping 2 ...
thread: I'm sleeping 3 ...
thread: I'm sleeping 4 ...
取消父协程会导致子协程也跟着取消
父协程手动调用cancel()或者异常结束,会立即取消它的所有子协程。
下例cancel()父协程job,其neibu内部的子协程都会跟着也cancel掉。
fun cancel() = runBlocking {
val job = launch {
launch(Dispatchers.Default) {
var nextPrintTime = System.currentTimeMillis()
var i = 0
//isActive是协程扩展属性,cancel()后变为false
while (i < 5 && isActive) {
// 一个执行计算的循环,只是为了占用 CPU
if (System.currentTimeMillis() >= nextPrintTime) {
println("child launch: I'm sleeping ${i++} ...")
// 每秒打印消息两次
nextPrintTime += 500L
}
}
}
async(Dispatchers.IO) {
var nextPrintTime = System.currentTimeMillis()
var i = 0
//isActive是协程扩展属性,cancel()后变为false
while (i < 5 && isActive) {
// 一个执行计算的循环,只是为了占用 CPU
if (System.currentTimeMillis() >= nextPrintTime) {
println("child async: I'm sleeping ${i++} ...")
// 每秒打印消息两次
nextPrintTime += 500L
}
}
}
}
delay(1000)
job.cancel()
println("cancel parent job")
delay(10 * 1000)
}
打印出:
child launch: I'm sleeping 0 ...
child async: I'm sleeping 0 ...
cancel parent job