书接上回:Android Kotlin知识汇总(三)Kotlin 协程
协程的并发问题
在一个协程中,循环创建10个子协程且单独运行各自Default线程中,并让每个子协程对变量 i 进行1000次自增操作。示例如下:
fun main() = runBlocking {var i = 0repeat(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@Synchronizedfun 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: Booleanpublic 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 = 0repeat(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,获取计算结果。