面试题答案
一键面试使用Mutex实现多线程同步及避免数据竞争
在Kotlin中,可以使用 kotlinx.coroutines.sync.Mutex
来实现多线程同步。以下是一个简单的示例:
import kotlinx.coroutines.*
import kotlinx.coroutines.sync.Mutex
// 创建一个Mutex实例
val mutex = Mutex()
// 共享资源
var sharedResource = 0
fun main() = runBlocking {
// 创建多个协程来模拟多线程访问
val jobs = List(10) {
launch {
mutex.lock()
try {
// 访问共享资源
sharedResource++
println("Thread ${Thread.currentThread().name} incremented sharedResource to $sharedResource")
} finally {
mutex.unlock()
}
}
}
jobs.forEach { it.join() }
}
在上述代码中:
- 首先创建了一个
Mutex
实例mutex
。 - 定义了共享资源
sharedResource
。 - 在协程中,通过
mutex.lock()
来获取锁,只有获取到锁的协程才能进入临界区访问共享资源。 - 使用
try - finally
块确保在访问完共享资源后,无论是否发生异常,都能通过mutex.unlock()
释放锁,从而让其他协程有机会获取锁并访问共享资源。这样就防止了多个线程同时访问共享资源,避免了数据竞争问题。
Mutex加锁和解锁过程中的注意事项
- 锁的获取与释放匹配:必须确保每次调用
lock()
都有对应的unlock()
调用。使用try - finally
块可以有效保证这一点,即使在临界区内发生异常,锁也能被正确释放。如果没有正确释放锁,会导致死锁,其他等待该锁的线程将永远无法获取锁。 - 避免锁的滥用:虽然Mutex能解决数据竞争问题,但过多地使用锁会降低程序的并发性能。因为每次只有一个线程能获取锁进入临界区,所以应尽量缩小临界区的范围,只在真正需要保护共享资源的代码段加锁。
- 锁的性能开销:获取和释放锁本身也有一定的性能开销,尤其是在高并发场景下。因此,要权衡使用Mutex带来的同步安全性和性能损失之间的关系。
- 死锁风险:除了未正确释放锁导致死锁外,如果多个线程以不同顺序获取多个锁,也可能导致死锁。例如,线程A获取锁1后尝试获取锁2,而线程B获取锁2后尝试获取锁1,此时就可能发生死锁。需要通过合理设计锁的获取顺序等方式来避免这种情况。