Kotlin中多Flow并发访问同一变量的线程安全性问题
代码线程安全性分析与解决方案
结论
这段代码不具备线程安全性,存在竞态条件问题,必须添加同步机制或改用更安全的Flow组合方式。
问题原因
- 共享变量并发读写:
listAll、list1、list2是跨协程共享的可变变量,两个子协程会同时对它们进行读写操作。即使在主线程执行,协程调度切换也会破坏操作原子性,导致listAll内容出现错误组合,或是被协程互相覆盖结果。 - 非原子性操作:每个collect块内的赋值、列表构建操作不是原子的。比如协程A刚更新完
list1,还没完成listAll构建,协程B就更新了list2,此时协程A构建的listAll会错误地使用新list1与旧list2的组合。
解决方案
方案一:手动添加同步锁
用synchronized块包裹所有对共享变量的读写逻辑,确保同一时间只有一个协程能执行这些操作:
viewModelScope.launch { val lock = Any() // 专用锁对象 var listAll = mutableListOf<String>() var list1 = mutableListOf<String>() var list2 = mutableListOf<String>() launch { source1.getNames().collect { list -> synchronized(lock) { list1 = list listAll = mutableListOf<String>().apply { addAll(list1) addAll(list2) } // 后续消费listAll的逻辑(比如emit到StateFlow) } } } launch { source2.getNames().collect { list -> synchronized(lock) { list2 = list listAll = mutableListOf<String>().apply { addAll(list2) addAll(list1) } // 后续消费listAll的逻辑(比如emit到StateFlow) } } } }
方案二:使用Flow的combine操作符(推荐)
Kotlin Flow内置的combine操作符会自动处理两个Flow的同步组合,每次任意一个Flow发射新值时,都会用最新的两个值生成组合结果,无需手动管理锁:
// 先组合两个Flow val combinedNamesFlow = combine(source1.getNames(), source2.getNames()) { list1, list2 -> mutableListOf<String>().apply { addAll(list1) addAll(list2) } } // 收集组合后的结果 viewModelScope.launch { combinedNamesFlow.collect { listAll -> // 消费listAll的逻辑(比如emit到StateFlow) } }
这种方式更符合Kotlin协程与Flow的设计理念,代码更简洁且不易出错。
内容的提问来源于stack exchange,提问作者Ригхард Чан
相关产品推荐
相关产品推荐

