Mutex

183 阅读2分钟

前言

在Java/Kotlin如果某段时间同一时间只有一个线程能够执行时,java通常用synchronized。协程中不能使用synchronized。Kotin协程中使用Mutex(不允许重入的互斥锁)。

suspend fun fun1(): String {
    return "fun1"
}

suspend fun fun2(): String {
    return "fun2"
}

suspend fun fun3(): String {
    synchronized(this) {
        // 编译不过,报异常
        val fun1 = fun1()
        val fun2 = fun2()
        return fun1 + fun2
    }
}

协程调用suspend方法相当于调用了一个异步函数,后续的恢复执行时就相当于异步函数的回调成功。

val lock = Mutex()
suspend fun fun4(): String {
    lock.lock()
    val fun1 = fun1()
    val fun2 = fun2()
    lock.unlock()
    return fun1 + fun2
}

Java中的可重入锁ReentrantLock也是不可以对协程代码加锁的。ReentrantLock的锁是针对线程的,在某个线程中获取了锁,也一定要在那个线程释放锁。而协程挂起前执行的线程和恢复所在的线程不一定是同一个线程,所以不可以使用ReentrantLock。协程中的代码执行的线程都是由ContinuationInterceptor来决定的,通常是通过线程池来管理,在挂起前执行的线程,在执行挂起后,这个线程也就闲置了,闲置后他还可以去做其他的工作,挂起的协程恢复后需要线程执行,但是挂起前的线程由别的事情要做处于忙碌状态,这样ContinuationInterceptor就需要指定一个新的线程去执行恢复后的任务。挂起前后恢复后的线程就不一致了。

Java中线程安全的集合类,如:ConcurrentHashMap可以使用,因为他们的内部没有suspend函数。只要没有使用synchroized或者ReentrantLock来锁suspend的函数都是可以的。

总结

Mutex和ReentrantLock与synchronized最大的区别是它是非重入锁,而且它不以线程作为锁的拥有者,专门为协程设计它获取锁的方式是lock方法,而且它是一个suspend方法,不过释放锁的unlock方法它不是一个suspend方法。ReentrantLock与synchronized都有可能会使用monitor锁,但是Mutext不会使用的,它通过CAS自旋的方式修改各种状态,这样做到线程安全。