书接上回:Android Kotlin知识汇总(三)Kotlin 协程
协程的并发问题
在一个协程中,循环创建10个子协程且单独运行各自Default线程中,并让每个子协程对变量 i 进行1000次自增操作。示例如下:
fun main() = runBlocking {
var i = 0
repeat(10) {
val job = launch(Dispatchers.Default) {
repeat(1000) {
i++
}
}
}
println("i: $i")
}
/*
输出信息:i: 9310
*/
此时,这10个协程运行在不同的线程中,可能会出现并发问题,最终结果小于、等于10000。
解决并发问题
我们知道,在 Java 中最简单的同步方式是 synchronized、Atomic、Lock等同步手段。事实上,在Kotlin 协程中也是可以同样适用这些同步手段的。
Kotlin特性之一:与 Java 的互操作性。由于 Kotlin 代码可编译为 JVM 字节码,意味着 Kotlin 利用现有的 Java 库直接调用。
Synchronized
使用 @Synchronized
注解修饰函数或 synchronized(){}
fun main() = runBlocking {
var i = 0
@Synchronized
fun add() {
i++
}
repeat(10) {
val job = launch(Dispatchers.Default) {
repeat(1000) {
add()
}
}
}
println("i: $i")
}
//输出信息:i: 10000
synchronized 问题
虽然 Kotlin 协程是基于 Java 线程的,但是它已经脱离了 Java 原本的范畴。
如果在 synchronized(){}
中调用suspend挂起函数,编译器会报错。
挂起函数会被翻译为 Continuation 的异步函数,造成 synchronized 代码块无法处理同步。
suspend fun add() {
i++
}
Mutex 同步锁
因为是Java 的锁是阻塞式的,会影响协程的非阻塞式特性,所以在 Kotlin 协程中,不推荐使用 Java 中的同步锁。
Kotlin 官方提供了非阻塞式的锁:Mutex。
public interface Mutex {
public val isLocked: Boolean
public suspend fun lock(owner: Any? = null)
public fun unlock(owner: Any? = null)
}
Mutex 是一个接口,lock() 方法是一个挂起函数,支持挂起和恢复,这是一个非阻塞式同步锁。
为了简化try、catch、lock、unlock的模板代码,Mutex提供了withLock{} 扩展函数。
public suspend inline fun <T> Mutex.withLock(owner: Any? = null, action: () -> T): T {
lock(owner)
try {
return action()
} finally {
unlock(owner)
}
}
使用示例如下:
fun main() = runBlocking {
val mutex = Mutex()
var i = 0
repeat(10) {
val job = launch(Dispatchers.Default) {
repeat(1000) {
//模板代码
try {
mutex.lock()
i++
} catch (e: Exception) {
println(e)
} finally {
mutex.unlock()
}
//简化代码
mutex.withLock {
i++
}
}
}
}
println("i: $i")
}
//输出信息:i: 10000
另外还有Actor 并发同步方式,本质是 Channel管道消息的简单封装。在 actor{} 外部,发送了10000次 AddMsg 消息,最后发送一次 ResultMsg,获取计算结果。