多个协程修改同一个变量,协程并发不安全例如:
class Test {
suspend fun massiveRun(action: suspend () -> Unit) {
val n = 100 // 启动的协程数量
val k = 1000 // 每个协程重复执行同一动作的次数
val time = measureTimeMillis {
coroutineScope { // 协程的作用域
repeat(n) {
launch {
repeat(k) { action() }
}
}
}
}
Log.d(TAG, "Completed ${n * k} actions in $time ms")
}
/**
* 初始值
*/
var counter = 0
fun main() = runBlocking {
withContext(Dispatchers.Default) {
massiveRun {// 多个协程协程修改同一个变量,存在线程安全问题:导致和实际结果不一致。
counter++
}
}
Log.d(TAG, "Counter = $counter")
}
}
// 输出:Completed 100000 actions in 23 ms
// Counter = 79374
通过线程安全类AtomicInteger,原子操作,保证并发安全。
/**
* 线程安全的类
*/
var counter = AtomicInteger(0)
fun main() = runBlocking {
withContext(Dispatchers.Default) {
massiveRun {// 通过安全操作方法来更新计数器
counter.incrementAndGet()
}
}
Log.d(TAG, "Counter = $counter")
}
输出:Completed 100000 actions in 22 ms
Counter = 100000
通过互斥锁Mutex
var counter = 0
/**
* 互斥锁
*/
val mutex = Mutex()
fun main() = runBlocking {
withContext(Dispatchers.Default) {
massiveRun {// 通过安全操作方法来更新计数器
mutex.withLock {
counter++
}
}
}
Log.d(TAG, "Counter = $counter")
}
输出:Completed 100000 actions in 345 ms
Counter = 100000
性能低,耗时明显增大。 MutableStateFlow
通过Channel实现安全并发
// 通过Channel
private val counterChannel = Channel<Int>(Channel.UNLIMITED)
suspend fun getCounter(): Int {
var counter = 0
for (value in counterChannel) {
counter += value
}
return counter
}
fun main() = runBlocking {
withContext(Dispatchers.Default) {
massiveRun {
counterChannel.send(1)
}
counterChannel.close()
}
val counter = getCounter()
Log.d(TAG, "Counter = ${counter}")
}
输出:
Completed 100000 actions in 103 ms
Counter = 100000
耗时明显增加,
/**
* 协程中的一个并发安全的、可观察的状态容器
*/
val counterFlow = MutableStateFlow(0)
fun main() = runBlocking {
withContext(Dispatchers.Default) {
massiveRun {
counterFlow.update { currentValue -> currentValue + 1 } // 使用 update 函数进行原子更新
}
}
Log.d(TAG, "Counter = ${counterFlow.value}")
}
输出:
Completed 100000 actions in 53 ms
Counter = 100000
launch单线程调度
/**
* 在单一线程中操作
*/
val singleThreadContext = newSingleThreadContext("SingleThread")
suspend fun massiveRun(action: suspend () -> Unit) {
val n = 100 // 启动的协程数量
val k = 1000 // 每个协程重复执行同一动作的次数
val time = measureTimeMillis {
coroutineScope { // 协程的作用域
repeat(n) {
launch(singleThreadContext) {
repeat(k) { action() }
}
}
}
}
Log.d(TAG, "Completed ${n * k} actions in $time ms")
}
var counter = 0
fun main() = runBlocking {
withContext(Dispatchers.Default) {
massiveRun {
counter++;
}
}
Log.d(TAG, "Counter = ${counter}")
}
输出:
Completed 100000 actions in 22 ms
Counter = 100000
总结:
| 方案 | 优点 | 缺点 | 适用场景 | 大概耗时 |
|---|---|---|---|---|
| 线程安全数据结构 | 简单高效,直接使用 | 适用范围有限,仅支持固定结构 | 简单计数器 | 22ms |
| Mutex | 灵活,适合复杂临界区 | 可能导致性能下降,存在锁竞争 | 多协程并发修改同一数据 | 345ms |
| Channel | 生产-消费模型 | 复杂度高,可能影响性能 | 消息队列、事件传递 | 103ms |
| StateFlow | 简化状态管理 | 性能有限,高频操作不适用 | UI状态同步 | 53ms |
| 单线程调度器 | 逻辑简单 | 性能不高 | 严格顺序依赖场景 | 22ms |