Kotlin基础——异步和并发

同步和异步

  • 同步指的是一种行为:当执行IO操作的时候,在代码层面上我们需要主动去等待结果,直到结果返回
  • 阻塞指的是一种状态:当执行IO操作的时候,线程处于挂起状态,就是该线程没有执行了

故同步不是阻塞,同步也可以是非阻塞的,如在执行同步代码块时,线程可以不阻塞而是一直在后台运行

代码中一般通过和多线程和回调来实现异步非阻塞

但多线程只是看上去同时执行,底层原理是通过CPU调度来实现的,当一个线程切换到另一个线程时,通常需要

  • 保存当前线程的执行上下文
  • 载入另一个线程的执行上下文

切换线程也是需要开销的,故当线程切换很频繁时,可能会导致多线程并不优于单线程

协程Coroutine

大量回调会使代码更加复杂,且会存在多层次的回调,同时线程切换的开销不可忽略,而协程则可以避免这些问题

协程是一个无优先级的子程序调度组件,允许子程序在特定的地方挂起恢复

  • 进程包含线程,线程包含协程

  • 一个线程可以有任意多个协程

  • 某一时刻只能由一个协程在运行,多个协程分享该线程分配到的计算机资源

    implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.4.1"
    implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.4.1"

使用Coroutine需要导入包,如下通过launch构造了一个协程,通过delay()挂起协程,但不会阻塞线程

复制代码
GlobalScope.launch {
    delay(1000L)
    println("World")
}
println("Hello, ")
Thread.sleep(2000L)

线程是由操作系统来调度的,而协程的切换可以由程序自己来控制,协程可以创建很多个,而线程是有限的

launch和runBlocking

  • delay只能在协程内部使用,用于挂起协程,不会阻塞线程
  • sleep用来阻塞线程

未避免混淆,可以使用runBlocking创建主协程,而使用launch创建子协程,从而在内部都使用delay(),但需要注意,runBlocking依旧会阻塞当前执行的线程

复制代码
fun test() = runBlocking {
    GlobalScope.launch {
        delay(1000L)
        println("World")
    }
    println("Hello, ")
    delay(2000L)
}

协程声明周期和join

当执行耗时操作,但并不知道需要多久时,为使程序一直保活,可以使用join

  • 如下程序会一直等待,直到协程结束,这里的等待是非阻塞式,不会将当前线程挂起

  • suspend修饰的方法只能在协程内部或其他suspend方法中使用

    fun test() = runBlocking {
    val job = launch {
    search()
    }
    println("Hello,")
    job.join()
    }

    suspend fun search() {
    delay(1000L)
    println("World")
    }

用同步方式写异步代码

在下面代码中,两个方法是顺序执行的

复制代码
fun test() = runBlocking<Unit> {
    val one = searchOne()
    val two = searchTwo()
    println("search is ${one} and ${two}")
}

suspend fun searchOne() {
    delay(3000L)
    println("one")
}

suspend fun searchTwo() {
    delay(1000L)
    println("two")
}

打印如下

复制代码
one
two
search is kotlin.Unit and kotlin.Unit

为了让其并行执行,可以使用async和await

  • 使用async相当于创建了一个子协程,会和其他子协程一样并行工作

  • async返回Deferred,是一个非阻塞可取消的future,其是一个带有结果的job,而Launch也会返回一个job但无返回值

  • future的意思是将来会返回一个结果,利用await可以等待返回值查询到之后获取出来

    fun test() = runBlocking<Unit> {
    val one = async { searchOne() }
    val two = async { searchTwo() }
    println("search is {one.await()} and {two.await()}")
    }

打印如下

复制代码
one
two
search is kotlin.Unit and kotlin.Unit

共享资源控制

如对于下面的数据

复制代码
val goods = hashMapOf<Long, Int>()
goods.put(1, 10)
goods.put(2, 15)

Synchronized

使用@Synchronized或synchronized()实现加锁

复制代码
@Synchronized
fun buyGoods(id: Long) {
    val stock = goods.getValue(id)
    goods.put(id, stock - 1)
}
fun buyGoods2(id: Long) {
    synchronized(this) {
        val stock = goods.getValue(id)
        goods.put(id, stock - 1)
    }
}

Lock

复制代码
var lock: Lock = ReentrantLock()
fun buyGoods3(id: Long) {
    lock.lock()
    try {
        val stock = goods.getValue(id)
        goods.put(id, stock - 1)
    } catch (ex: Exception) {
        println(ex)
    } finally {
        lock.unlock()
    }
}

上面写法有以下问题:

  • 若有多个同步方法,将会竞争同一把锁

  • 加锁后可能忘记解锁

  • 重复的模板代码

    fun <T> withLock(lock: Lock, action: () -> T) {
    lock.lock()
    try {
    action()
    } catch (ex: Exception) {
    println(ex)
    } finally {
    lock.unlock()
    }
    }
    fun buyGoods(id: Long) {
    val stock = goods.getValue(id)
    goods.put(id, stock - 1)
    }
    var lock: Lock = ReentrantLock()
    withLock(lock, { buyGoods(1) })

上面使用高阶函数进行了优化,库函数也自带withLock()方法

复制代码
fun buyGoods(id: Long) {
    val stock = goods.getValue(id)
    goods.put(id, stock - 1)
}
var lock: Lock = ReentrantLock()
lock.withLock({ buyGoods(1) })
相关推荐
阿巴斯甜3 小时前
Android 报错:Zip file '/Users/lyy/develop/repoAndroidLapp/l-app-android-ble/app/bu
android
Kapaseker4 小时前
实战 Compose 中的 IntrinsicSize
android·kotlin
xq95275 小时前
Andorid Google 登录接入文档
android
黄林晴6 小时前
告别 Modifier 地狱,Compose 样式系统要变天了
android·android jetpack
冬奇Lab18 小时前
Android触摸事件分发、手势识别与输入优化实战
android·源码阅读
城东米粉儿21 小时前
Android MediaPlayer 笔记
android
Jony_1 天前
Android 启动优化方案
android
阿巴斯甜1 天前
Android studio 报错:Cause: error=86, Bad CPU type in executable
android
张小潇1 天前
AOSP15 Input专题InputReader源码分析
android
_小马快跑_1 天前
Kotlin | 协程调度器选择:何时用CoroutineScope配置,何时用launch指定?
android