Kotlin基础——异步和并发

06-29 1781阅读

同步和异步

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

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

    Kotlin基础——异步和并发
    (图片来源网络,侵删)

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

    但多线程只是看上去同时执行,底层原理是通过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 {
                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 {
                  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()
              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  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) })
                
VPS购买请点击我

文章版权声明:除非注明,否则均为主机测评原创文章,转载或复制请以超链接形式并注明出处。

目录[+]