Mutex 使用场景
Mutex互斥锁,可防止多个协程同时执行代码的特定部分。 简单来说,它是一种锁,确保在给定时间只有一个协程可以访问特定资源或执行特定任务。如下场景时可以考虑使用互斥锁:
- 资源保护:互斥有助于保护共享资源同时访问,从而确保线程安全。
- 顺序维持:确保任务按照预期的顺序执行。
- 死锁预防:正确使用互斥锁可以帮助避免应用程序中的死锁。
Mutex 有如下常用 api:
- lock():获取锁。 如果锁已经被持有,协程将挂起,直到锁可用。
- unlock():释放锁,允许其他协程获取它。unlock 一定要释放,为了确保释放推荐使用 Mutex 的扩展函数 withLock()。
- holdsLock() 方法用于检查当前线程是否持有锁。
- tryLock() 方法用于尝试获取锁。如果成功,则会立即返回。如果失败,则会立即返回。
- onLock 属性用于指定协程在获取锁时要执行的操作。
var value = 0
val mutex = Mutex()
repeat(100) {
scope.launch(Dispatchers.Default) {
mutex.withLock { // 加锁保证value值的正常累加
value++
}
}
}
fun testMutexOrder() {
scope.launch { job1() }
scope.launch { job2() }
}
private suspend fun job2() {
mutex.withLock {
Log.d(TAG, "job2")
}
}
private suspend fun job1() {
mutex.withLock {
delay(2000)
Log.d(TAG, "job1")
}
}
// job1 执行完后才执行 job2
Mutex 不会产生阻塞
Mutex的实现基于挂起函数和协程的概念。当一个协程请求进入受Mutex保护的临界区时,如果Mutex已经被占用,请求的协程将被挂起,直到Mutex可用。此过程不会产生阻塞。
Mutex 是不可重入的
它是不可重入的,即使从当前持有锁的同一线程/协程调用锁,仍然会挂起调用者。这是与 synchronized 其中的一处不同。
fun testReentrant() {
val pool = Executors.newCachedThreadPool()
repeat(2) {
scope.launch(pool.asCoroutineDispatcher()) {
mutex.withLock {
Log.d(TAG, "testReentrant before: ${Thread.currentThread().name}")
delay(2000)
Log.d(TAG, "testReentrant after: ${Thread.currentThread().name}")
}
}
}
}
//两个线程按顺序执行
//08:30:00.062 testReentrant before: pool-thread-1
//08:30:02.064 testReentrant after: pool-thread-1
//08:30:02.065 testReentrant before: pool-thread-2
//08:30:04.068 testReentrant after: pool-thread-2
避免死锁
在使用Mutex时,互斥体不能在没有首先解锁的情况下被锁定两次。即协程获取Mutex后未释放就被挂起,导致其他协程无法继续执行。虽然产生死锁,但不会产生 anr,因为是基于挂起实现的。
fun main() {
launch { firstLock() }
}
private val mutex = Mutex()
private suspend fun firstLock() {
mutex.withLock {
Log.d(TAG, "firstLock: ")
secondLock()
}
}
private suspend fun secondLock() {
mutex.withLock {
delay(2000)
Log.d(TAG, "secondLock: after")
}
}
secondLock的日志不会打印。