前言
在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自旋的方式修改各种状态,这样做到线程安全。