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) })
相关推荐
老猿讲编程26 分钟前
一个例子来说明Ada语言的实时性支持
开发语言·ada
Chrikk1 小时前
Go-性能调优实战案例
开发语言·后端·golang
幼儿园老大*1 小时前
Go的环境搭建以及GoLand安装教程
开发语言·经验分享·后端·golang·go
canyuemanyue1 小时前
go语言连续监控事件并回调处理
开发语言·后端·golang
杜杜的man1 小时前
【go从零单排】go语言中的指针
开发语言·后端·golang
数据猎手小k2 小时前
AndroidLab:一个系统化的Android代理框架,包含操作环境和可复现的基准测试,支持大型语言模型和多模态模型。
android·人工智能·机器学习·语言模型
萧鼎3 小时前
Python并发编程库:Asyncio的异步编程实战
开发语言·数据库·python·异步
学地理的小胖砸3 小时前
【一些关于Python的信息和帮助】
开发语言·python
疯一样的码农3 小时前
Python 继承、多态、封装、抽象
开发语言·python
^velpro^3 小时前
数据库连接池的创建
java·开发语言·数据库