阻塞线程 = 线程被"钉死"在原地,干瞪眼等着,别的啥也干不了
不阻塞线程 = 线程标记一下"这事我记着,先去干别的",等条件满足了再回来继续
用餐厅服务员类比
假设一个服务员(线程)要服务两桌客人:
阻塞(Blocking)
服务员给A桌下单后,站在收银台前一动不动,盯着出餐口等菜做好。B桌客人喊破喉咙他也听不见,因为他人被"卡"在这了。
kotlin
// 阻塞线程:线程干等着,啥也不干
fun fetchData(): String {
val response = httpClient.execute(request) // 线程卡在这里等网络响应
// 网络没回来之前,这行代码下面的都执行不了
return response.body
}
不阻塞 / 挂起(Non-blocking / Suspend)
服务员给A桌下单后,记在小本上,转身去给B桌点菜、给C桌加水。后厨喊"A桌好了",他再回来端菜。
kotlin
// 挂起协程:线程被释放,可以去执行其他协程
suspend fun fetchData(): String {
val response = httpClient.executeAsync(request).await() // 协程挂起
// 等待期间,线程去执行别的协程任务了
return response.body
}
代码层面的区别
表格
| 阻塞线程 | 不阻塞线程(挂起) | |
|---|---|---|
| 线程状态 | RUNNABLE 但卡在等待,CPU 空转或内核挂起 |
协程挂起,线程被释放去执行别的任务 |
| 资源占用 | 占着线程不干活 | 线程可以服务其他协程 |
| 代码写法 | 直接调用函数 | 需要 suspend + await |
| 并发能力 | 1个线程只能做1件事 | 1个线程可以同时"跟进"上百个协程 |
一个更直观的对比
kotlin
// ========== 阻塞线程 ==========
fun blockingWay() {
val result1 = fetchFromNetwork() // 耗时2秒,线程卡死2秒
val result2 = fetchFromNetwork() // 再卡2秒
// 总共4秒,线程全程被占着
}
// ========== 不阻塞线程 ==========
suspend fun nonBlockingWay() = coroutineScope {
val deferred1 = async { fetchFromNetworkAsync() } // 启动,不等待
val deferred2 = async { fetchFromNetworkAsync() } // 立刻启动第二个
val result1 = deferred1.await() // 如果需要,这里挂起协程(不是线程)
val result2 = deferred2.await()
// 两个请求同时发,大约2秒完成,等待期间线程去干别的了
}
一句话总结
阻塞线程 是"人(线程)被活拴在一件事上,别的干不了";
不阻塞线程是"事(协程)被挂起来记着,人(线程)先去忙别的,回头再来"。
协程的 await 挂起的是协程 ,释放的是线程。这是它能用少量线程处理大量并发请求的根本原因。