在Kotlin协程中编写阻塞代码是否仍会阻塞线程?两种实现差异问询
两种实现的线程阻塞表现差异
两种方案在线程阻塞表现上确实存在显著差异,你的同事的判断是正确的:
- 原有实现的问题:
suspendCoroutine的执行逻辑是:立刻在当前协程所处的线程上执行传入的lambda参数,只有当lambda执行完成后没有主动调用resume、而是将continuation实例保存起来等待后续异步触发resume时,协程才会真正挂起、释放当前线程。
你在lambda内部直接调用了同步阻塞的executeQuery方法,在这个方法等待网络IO返回的整个过程中,当前线程一直被阻塞占用,直到方法返回、你主动调用continuation.resume,整个过程协程根本没有发生真正的挂起,本质就是在协程里跑阻塞代码,和直接在普通线程里跑同步方法没有区别,完全浪费了协程的非阻塞能力。 - 优化后实现的优势:
reactiveExecuteQuery是WebFlux提供的非阻塞API,调用后会立刻返回Mono对象,不会阻塞当前线程。后续调用awaitSingle()时,协程会真正挂起,当前线程被释放可以处理其他任务,直到Mono返回结果或报错时,协程才会被恢复继续执行后续逻辑,全程不会阻塞线程,资源利用率远高于原有实现。
suspendCoroutine的适用场景
suspendCoroutine的核心作用是将基于回调的异步API封装为Kotlin挂起函数,只适合用来对接异步回调类的旧API,不适合包裹同步阻塞代码。
举个常见的正确使用示例:假设你有如下回调式异步API:
void asyncExecuteQuery(QueryRequest request, Callback<QueryResponse> callback);
你就可以用suspendCoroutine封装为挂起函数:
private suspend fun <T: Any> request(queryRequest: GraphQLQueryRequest<T>): T? = suspendCoroutine { cont -> asyncExecuteQuery(queryRequest) { response, error -> if (error != null) { cont.resumeWithException(error) } else { val item = response.extractValueAsObject("data.${queryRequest.pathAccessor}", queryRequest.type.java) cont.resume(item) } } }
这种场景下,lambda执行完注册回调的逻辑就结束了,continuation被保存在回调实例里,协程会挂起释放线程,等到异步回调触发时才恢复,完全不会阻塞线程。
另外实际开发中更推荐使用suspendCancellableCoroutine,它支持协程取消逻辑,可以在协程被取消时同步取消对应的异步请求,避免资源泄漏。
内容的提问来源于stack exchange,提问作者Lucas
相关产品推荐
相关产品推荐

