一、Mutex简介
Mutex(互斥锁) 是Kotlin协程中用于实现互斥访问共享资源的核心工具。它确保同一时间只有一个协程可以进入临界区(Critical Section),从而避免数据竞争和不一致问题。与线程锁不同,协程的Mutex基于挂起机制而非阻塞,更轻量且适合协程的协作式多任务模型。
二、核心特性
- 互斥访问:同一时刻仅允许一个协程持有锁。
- 挂起而非阻塞:未获取锁的协程会被挂起,不占用线程资源。
- 可取消性:协程被取消时自动释放锁,避免死锁。
- 不可重入:同一协程重复获取锁会导致挂起(需手动设计避免)。
三、使用场景
- 共享变量修改:如多个协程并发修改计数器。
- 复杂资源操作:操作非线程安全的集合(如
ArrayList)。 - 跨协程状态同步:确保多个协程按特定顺序访问资源。
- 替代
synchronized:在协程环境中更高效地实现互斥。
四、示例代码
1. 基本用法(保护计数器)
import kotlinx.coroutines.*
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
val mutex = Mutex()
var counter = 0
fun main() = runBlocking {
val jobs = List(10) { // 启动10个协程
launch(Dispatchers.Default) {
repeat(1000) { // 每个协程递增计数器1000次
mutex.withLock { // 自动加锁/解锁
counter++
}
}
}
}
jobs.forEach { it.join() }
println("Final counter: $counter") // 正确输出10000
}
2. 保护共享集合
val mutex = Mutex()
val sharedList = mutableListOf<Int>()
fun updateList(value: Int) = runBlocking {
mutex.withLock {
sharedList.add(value)
println("Added $value, list size: ${sharedList.size}")
}
}
// 多个协程并发调用 updateList()
3. 结合超时控制
suspend fun tryLockWithTimeout() {
try {
// 尝试在500ms内获取锁
if (mutex.tryLock(500)) {
try {
// 临界区操作
delay(300) // 模拟耗时操作
} finally {
mutex.unlock()
}
} else {
println("Failed to acquire lock within timeout")
}
} catch (e: CancellationException) {
println("Lock acquisition cancelled")
}
}
五、API详解
| 方法 | 描述 |
|---|---|
mutex.lock() | 挂起协程直至获取锁,需手动调用unlock()。 |
mutex.unlock() | 释放锁,必须在持有锁的协程中调用。 |
mutex.withLock { ... } | 自动管理锁的获取和释放(推荐方式)。 |
mutex.tryLock() | 非挂起方式尝试立即获取锁,返回true/false。 |
mutex.holdsLock() | 检查当前协程是否持有锁。 |
六、注意事项
1. 避免死锁:
-
不可重入:同一协程重复获取锁会导致永久挂起。
// 错误示例:导致死锁 mutex.withLock { mutex.withLock { // 第二次尝试获取同一锁 // 永远不会执行 } }
2. 锁粒度控制:
-
尽量缩小临界区范围,避免长时间持有锁。
// 不推荐:锁内执行耗时操作 mutex.withLock { delay(1000) // 长时间挂起阻塞其他协程 updateResource() }
3. 协程取消处理:
-
协程取消时自动释放锁,但需确保资源状态一致性。
mutex.withLock { try { criticalOperation() } finally { // 清理资源(即使协程被取消) } }
3. 性能考量:
- 高频锁竞争场景中,优先考虑原子变量(如
AtomicInteger)或无锁数据结构。
七、与其他并发工具对比
| 工具 | 特点 |
|---|---|
| Mutex | 互斥锁,适合保护共享资源的独占访问。 |
| Semaphore | 信号量,允许指定数量的协程同时访问资源。 |
| Atomic | 原子变量,轻量级解决简单变量的线程安全问题。 |
| Channel | 通过通信共享内存,适合生产者-消费者模式。 |
八、总结
-
何时使用Mutex:需要互斥访问复杂共享状态时。
-
最佳实践:
- 优先使用
withLock简化锁管理。 - 避免在锁内执行耗时或挂起操作。
- 结合协程取消机制确保资源释放。
- 优先使用
-
替代方案:简单场景下优先考虑原子变量或协程通道(Channel)。
通过合理使用Mutex,可以在协程并发编程中有效避免数据竞争,同时保持代码的高效与可维护性。