kotlin mutex 使用

880 阅读2分钟

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的日志不会打印。