含suspend与非suspend函数的API如何实现线程安全?
要解决这个问题,核心是不能混用Java内置锁(@Synchronized)和协程Mutex——因为@Synchronized绑定线程,协程挂起时不会释放线程锁,会导致其他调用bar()的线程被无意义阻塞,甚至引发死锁。正确的做法是统一用Kotlin协程的Mutex作为同步机制,同时兼容非suspend的bar()函数。
具体改造步骤:
- 移除原来的
@Synchronized注解,改用协程Mutex实例作为全局同步锁。 - 把原
runWithoutConcurrency()里的临界区逻辑抽成一个无同步注解的私有函数(比如criticalSection())。 - 将
foo()改为suspend函数,用mutex.withLock {}包裹临界区调用——这是协程安全的同步方式,挂起时会自动释放锁。 - 对于非suspend的
bar(),用runBlocking {}包裹mutex.withLock {},把协程同步逻辑转换成普通线程可调用的形式,行为和原来的@Synchronized一致,同时和foo()共享同一锁保证互斥。
改造后的完整代码:
@AnyThread class MyClass { // 全局协程互斥锁,统一同步所有临界区访问 private val mutex = Mutex() suspend fun foo(): Int { mutex.withLock { criticalSection() } // .. 原foo()的剩余逻辑 return 0 // 示例返回值 } @AnyThread fun bar(): Int { // 用runBlocking将协程同步逻辑适配为非suspend调用 runBlocking { mutex.withLock { criticalSection() } } // .. 原bar()的剩余逻辑 return 0 // 示例返回值 } // 抽离的临界区逻辑,无任何同步注解 private fun criticalSection() { /* 原runWithoutConcurrency()里的代码 */ } }
关键说明:
Mutex是协程感知的同步工具,不管是suspend函数还是通过runBlocking调用的普通函数,都能保证同一时间只有一个执行流进入临界区。runBlocking在这里仅用于适配非suspend场景,不会引入额外性能问题——它只会阻塞当前线程直到临界区执行完毕,和原来@Synchronized的阻塞行为完全一致。- 必须统一用
Mutex,不能让bar()继续用@Synchronized:两者锁的粒度不同(一个是协程锁,一个是线程锁),会导致临界区被同时访问,彻底破坏线程安全。
内容的提问来源于stack exchange,提问作者Vit Khudenko
相关产品推荐
相关产品推荐

