kotlin协程也有并发?

本文涉及的产品
日志服务 SLS,月写入数据量 50GB 1个月
简介: kotlin协程也有并发?

概览


我们在 java 中处理并发是家常便饭,但是协程的并发你有没有想过呢,协程是否也有java一样的并发问题?

我们知道协程是轻量级的进程,而且是可以多线程调度的。那么想想这样一个情景:

我们开启1000个协程,每个协程中对count进行自增,协程执行完成后能否拿到count==1000的结果,答案在后面的章节中,最终结论就是kkotlin也是需要处理并发的

那么这种并发该如何处理呢,我想先给你说的是,协程有自己的一套并发规则,你应该试图优先用 kotlin 的并发方法来处理协程的并发

本文主要讲几种协程处理并发的例子


模拟协程并发


模拟案例

既然是讲协程的并发,那么我们首先应该证明一下协程中存在并发问题,对吧,那就来吧。

先看一下模拟的代码:

  1. 代码
fun concurrent(){
    val scope = CoroutineScope(Dispatchers.Default)//创建协程作用域,Default支持并发
    var count=0
    repeat(1000){//重复1000次,每次开启一个协程,count自增1
        scope.launch {
            count++
            println(count)
        }
    }
}
fun main() = runBlocking {
    concurrent()
}
复制代码

本例中,我们模拟了这样一个场景,使用自定义的作用域 scope 短时间内开启 1000 个协程,协程中每次把 count 自增 1 打印数据。如果最终打印的数据是 1000 那么说明不存在并发,如果打印的数据小于 1000,那么说明协程中也存在如 java 一样的并发问题。


  1. 日志

这里只发出最后一部分的日志:

696
695
694
693
692
691
690
703
702
复制代码
  1. 结论

日志打印最终验证协程中也存在并发性的问题,那么后面的章节我们就拿出我们的协程解决方案吧。


模拟案例扩展

我们将本例中的代码做一个扩展,在concurrent方法尾部加一个1s的延时

  1. 代码

image.png


  1. 输出日志
999
1000
978
995
994
993
992
991
1000
复制代码
  1. 结论

当最后一个协程执行完成时可以计算出1000这个正确结果但是打印的中间值顺序却是错乱的(这一点和java不一样),但是我们在业务代码中却没有办法获取这个正确结果产生的时间,所以我们依然要处理并发问题


解决并发的方法


解决并发的问题既可以使用 java 中的部分方式,也可以使用 kotlin 自有的方式。协程自有的处理方法适用性更广一些。


java 方式解决

如果我们想使用java的方式解决并发,那么我们可以尝试让协程运行在单线程中,也可以尝试使用Atomic方式


单线程解决方法

我们将 模拟并发章节的代码进行一些改动试试:

  1. 代码
fun concurrent() {
    val scope = CoroutineScope(Dispatchers.Unconfined)//创建协程作用域,使用Unconfined,这样在协程被挂起前都不会改变线程,也就是说协程始终运行在单线程中
    var count = 0
    repeat(1000) {//重复1000次,每次开启一个协程,count自增1
        scope.launch {
            println("线程id:${Thread.currentThread().id}")//这个线程始终不会变,除非你在这里挂起
            count++
            println(count)
        }
    }
}
复制代码
  1. 日志
994
线程id:1
995
线程id:1
996
线程id:1
997
线程id:1
998
线程id:1
999
线程id:1
1000
复制代码
  1. 结论

本例代码中因为launch始终运行在单线程中所以最终输出count==1000,中间值的顺序也没有错乱


使用Atomic方式解决

上一小节中我们的代码始终运行在单线程中,所以最终输出正确结果,那么如果我们的代码不允许在单线程中有没有解决方法呢,答案当然是有, Atomic机制即可解决。

试想一下如果我们把上面的代码scope.launch 改为launch(Dispatchers.Default) ,那么线程就变成了运行在多线程中了,此时输出的最终结果一定不会是我们想要的(我已经帮你试过了,就不贴代码和日志了)

那么我们可以尝试用Atomic方式解决这个问题

  1. 代码
suspend fun concurrent() {
    var count =AtomicInteger(0)
    coroutineScope {
        repeat(10000) {//重复1000次,每次开启一个协程,count自增1
            launch(Dispatchers.Default) {
                count.incrementAndGet()
                println("计算中间值$count")
            }
        }
    }
    delay(1000)
    println("计算结果:$count")
}
复制代码
  1. 日志
计算结果:10000
复制代码
  1. 结论

虽然最终输出结果是正确的

但是对于负责的状态Atomic明显是处理不了的,负责的状态可以考虑后面要讲的Mutex方式


kotlin 方式解决

互斥

kotlin 为我们提供了Mutex实现线程安全,Mutex通俗点来说就是kotlin的锁,和java 的synchronized和RecentLock对应。

使用mutex.withLock {*} 即可实现数据的同步

看代码:

  1. 代码
val mutex = Mutex()
suspend fun concurrent() {
    var count =0
    coroutineScope {
        repeat(10000) {//重复1000次,每次开启一个协程,count自增1
            launch(Dispatchers.Default) {
                mutex.withLock {
                    count++
                }
                println("中间值:$count")
            }
        }
    }
    delay(1000)
    println("计算结果:$count")
}
复制代码
  1. 日志
****
中间值:9993
中间值:9994
中间值:9995
中间值:9996
中间值:9997
中间值:9998
中间值:9999
中间值:10000
计算结果:10000
复制代码
  1. 结论

使用Mutex方式解决并发,最终输出正确结果,并且中间值也是按顺序输出


结尾


掌握上面的几种方式基本上能处理大部分业务逻辑了

如果你有其他方案欢迎留言,我会及时补充



相关实践学习
日志服务之使用Nginx模式采集日志
本文介绍如何通过日志服务控制台创建Nginx模式的Logtail配置快速采集Nginx日志并进行多维度分析。
相关文章
|
20天前
|
消息中间件 算法 Java
(十四)深入并发之线程、进程、纤程、协程、管程与死锁、活锁、锁饥饿详解
本文深入探讨了并发编程的关键概念和技术挑战。首先介绍了进程、线程、纤程、协程、管程等概念,强调了这些概念是如何随多核时代的到来而演变的,以满足高性能计算的需求。随后,文章详细解释了死锁、活锁与锁饥饿等问题,通过生动的例子帮助理解这些现象,并提供了预防和解决这些问题的方法。最后,通过一个具体的死锁示例代码展示了如何在实践中遇到并发问题,并提供了几种常用的工具和技术来诊断和解决这些问题。本文旨在为并发编程的实践者提供一个全面的理解框架,帮助他们在开发过程中更好地处理并发问题。
|
2月前
|
安全 Android开发 Kotlin
Android面试题之Kotlin协程并发问题和互斥锁
Kotlin的协程提供轻量级并发解决方案,如`kotlinx.coroutines`库。`Mutex`用于同步,确保单个协程访问共享资源。示例展示了`withLock()`、`lock()`、`unlock()`和`tryLock()`的用法,这些方法帮助在协程中实现线程安全,防止数据竞争。
32 1
|
2月前
|
监控 程序员 调度
协程实现单线程并发(入门)
协程实现单线程并发(入门)
28 1
|
2月前
|
存储 Java 调度
Android面试题之Kotlin 协程的挂起、执行和恢复过程
了解Kotlin协程的挂起、执行和恢复机制。挂起时,状态和上下文(局部变量、调用栈、调度器等)被保存;挂起点通过`Continuation`对象处理,释放线程控制权。当恢复条件满足,调度器重新分配线程,调用`resumeWith`恢复执行。关注公众号“AntDream”获取更多并发知识。
58 2
|
3月前
|
移动开发 Android开发 开发者
构建高效Android应用:Kotlin与协程的完美融合
【5月更文挑战第25天】 在移动开发的世界中,性能和响应性是衡量应用质量的关键指标。随着Kotlin的流行和协程的引入,Android开发者现在有了更强大的工具来提升应用的性能和用户体验。本文深入探讨了Kotlin语言如何与协程相结合,为Android应用开发带来异步处理能力的同时,保持代码的简洁性和可读性。我们将通过实际案例分析,展示如何在Android项目中实现协程,以及它们如何帮助开发者更有效地管理后台任务和用户界面的流畅交互。
|
3月前
|
移动开发 数据库 Android开发
构建高效Android应用:探究Kotlin的协程优势
【5月更文挑战第22天】随着移动开发技术的不断进步,Android平台的性能优化已经成为开发者关注的焦点。在众多提升应用性能的手段中,Kotlin语言提供的协程概念因其轻量级线程管理和异步编程能力而受到广泛关注。本文将深入探讨Kotlin协程在Android开发中的应用,以及它如何帮助开发者构建出更高效、响应更快的应用,同时保持代码的简洁性和可读性。
|
3月前
|
移动开发 监控 Android开发
构建高效安卓应用:Kotlin 协程的实践与优化
【5月更文挑战第16天】 在移动开发领域,性能优化一直是开发者们追求的重要目标。特别是对于安卓平台来说,由于设备多样性和系统资源的限制,如何提升应用的响应性和流畅度成为了一个关键议题。近年来,Kotlin 语言因其简洁、安全和高效的特点,在安卓开发中得到了广泛的应用。其中,Kotlin 协程作为一种轻量级的并发解决方案,为异步编程提供了强大支持,成为提升安卓应用性能的有效手段。本文将深入探讨 Kotlin 协程在安卓开发中的应用实践,以及通过合理设计和使用协程来优化应用性能的策略。
41 8
|
3月前
|
移动开发 Android开发 开发者
构建高效安卓应用:Kotlin 协程的实践指南
【5月更文挑战第18天】 随着移动开发技术的不断进步,安卓平台亟需一种高效的异步编程解决方案来应对日益复杂的应用需求。Kotlin 协程作为一种新兴的轻量级线程管理机制,以其简洁的语法和强大的功能,成为解决这一问题的关键。本文将深入探讨Kotlin协程在安卓开发中的实际应用,从基本概念到高级技巧,为开发者提供一份全面的实践指南,旨在帮助读者构建更加高效、稳定的安卓应用。
|
3月前
|
移动开发 安全 Android开发
构建高效Android应用:Kotlin与协程的完美结合
【5月更文挑战第17天】 在移动开发领域,性能优化和流畅的用户体验是关键。对于Android平台而言,Kotlin语言凭借其简洁性和功能安全性成为开发的首选。与此同时,协程作为一种新的并发处理方式,在简化异步编程方面展现出巨大潜力。本文将深入探讨如何通过Kotlin语言以及协程技术,提升Android应用的性能和响应能力,并确保用户界面的流畅性。
|
3月前
|
移动开发 数据库 Android开发
构建高效Android应用:Kotlin与协程的完美结合
【5月更文挑战第7天】 在移动开发领域,性能优化和资源管理始终是核心议题。随着Kotlin语言的普及,其提供的协程特性为Android开发者带来了异步编程的新范式。本文将深入探讨如何通过Kotlin协程来优化Android应用的性能,实现流畅的用户体验,并减少资源消耗。我们将分析协程的核心概念,并通过实际案例演示其在Android开发中的应用场景和优势。