You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Kotlin协程并发场景下缓存层的重复请求规避方案咨询

解决协程下并发请求时的缓存层重复网络请求问题

好问题!你当前的实现确实存在并发缓存击穿的风险——当多个协程同时调用getItems()时,由于cachedItems还未被初始化,所有协程都会绕过缓存检查,发起重复的网络请求。用Kotlin协程处理这类场景,有两个非常实用的方案,我优先推荐第二种:

方案一:使用Mutex加锁保护缓存检查与初始化

Mutex是协程中的互斥锁,它能确保同一时间只有一个协程执行临界区代码,避免重复请求。我们可以用它包裹缓存初始化的逻辑:

class Repository(private val webServices: WebServices) {
    private var cachedItems: List<Item>? = null
    private val mutex = Mutex() // 协程安全的互斥锁

    suspend fun getItems(): List<Item> {
        // 先尝试直接返回缓存,避免每次都加锁
        cachedItems?.let { return it }

        // 加锁处理缓存初始化
        return mutex.withLock {
            // 二次检查!防止等待锁的过程中已有其他协程完成了缓存初始化
            cachedItems?.let { return it }

            val items = withContext(Dispatchers.IO) { webServices.getItems() }
            cachedItems = items
            items
        }
    }
}

这个方案逻辑直观,和传统多线程的锁思路一致,但每次缓存为空时都会有协程等待锁,适合缓存频繁失效的场景。

方案二:缓存Deferred对象(推荐)

更优雅的方式是缓存网络请求的Deferred对象,而不是最终的数据结果。Deferred本身就代表了一个异步任务,多个协程可以同时await()它的结果,且只会触发一次实际执行:

class Repository(private val webServices: WebServices) {
    private var cachedRequest: Deferred<List<Item>>? = null
    private val mutex = Mutex() // 保护cachedRequest的并发修改

    suspend fun getItems(): List<Item> {
        // 先尝试复用已有的请求
        cachedRequest?.let { return it.await() }

        return mutex.withLock {
            // 二次检查,避免竞态条件
            cachedRequest?.let { return it.await() }

            // 创建异步请求并缓存
            val request = CoroutineScope(Dispatchers.IO).async {
                webServices.getItems()
            }
            cachedRequest = request
            request.await()
        }
    }

    // 如果需要刷新缓存,只需清空cachedRequest即可
    suspend fun invalidateCache() {
        mutex.withLock {
            cachedRequest = null
        }
    }
}

为什么推荐这个方案?

  • 性能更优:只有第一次请求会触发网络调用,后续所有协程都直接等待同一个异步任务的结果,无需重复加锁等待缓存初始化。
  • 天然适配协程模型:Deferred是协程原生的异步任务抽象,比锁更贴合协程的非阻塞特性。
  • 缓存失效简单:如果需要刷新数据,只需将cachedRequest置为null,下一次调用getItems()就会重新发起请求。

额外注意事项

  • 如果你使用的是ViewModel或其他有生命周期的组件,要注意协程上下文的绑定,避免内存泄漏(比如可以用viewModelScope代替全局CoroutineScope)。
  • 如果你的缓存需要设置过期时间,可以结合flow和delay实现自动失效逻辑,不过这属于进阶场景了。

内容的提问来源于stack exchange,提问作者Chris Horner

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.14 08:02:22