Android面试题之Kotlin协程并发问题和互斥锁

简介: Kotlin的协程提供轻量级并发解决方案,如`kotlinx.coroutines`库。`Mutex`用于同步,确保单个协程访问共享资源。示例展示了`withLock()`、`lock()`、`unlock()`和`tryLock()`的用法,这些方法帮助在协程中实现线程安全,防止数据竞争。

本文首发于公众号“AntDream”,欢迎微信搜索“AntDream”或扫描文章底部二维码关注,和我一起每天进步一点点

Kotlin 语言提供了多种机制来处理并发和同步,其中包括高层次和低层次的工具。对于常规的并发任务,可以利用 Kotlin 协程提供的结构化并发方式。而对于需要更低层次的锁定机制,可以使用 Mutex 来实现对共享资源的线程安全访问。

Kotlin 协程与并发(Coroutines and Concurrency)

协程是一种轻量级的线程,可以通过 kotlinx.coroutines 库来实现。协程为结构化并发提供了强大的支持,使得编写异步、并发代码变得更加简单和直观。

协程基础

import kotlinx.coroutines.*

fun main() = runBlocking {
    launch {
        delay(1000L)
        println("World!")
    }
    println("Hello,")
}

在这个例子中,runBlocking 函数用于启动一个新的协程并阻塞当前线程,而 launch 函数则用于启动一个新的协程,并在1秒后输出 "World!"。

并发与同步

当多个协程需要访问共享资源时,需要一些同步机制来防止数据竞争。一个常用的方法是使用 Kotlin 库提供的 Mutex

Mutex

Mutex(互斥锁)是一种用于保证互斥访问共享资源的同步机制。Mutex 确保在同一时刻只有一个协程能够访问被保护的代码块或资源,从而避免竞争条件。

使用 Mutex

import kotlinx.coroutines.*
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock

val mutex = Mutex()
var counter = 0

fun main() = runBlocking {
    val jobs = List(100) {
        launch {
            repeat(1000) {
                // 在这里使用 mutex 来保护对 counter 的访问
                mutex.withLock {
                    counter++
                }
            }
        }
    }
    jobs.forEach { it.join() }
    println("Counter = $counter")
}

在这个例子中,我们创建了100个协程,每个协程重复1000次对共享变量 counter 的访问。使用 mutex.withLock 保证了每次只有一个协程能访问 counter,从而避免并发问题。

withLock() 是一种便捷方法,用于在锁内执行给定的代码块。它会自动处理获取和释放锁,确保即使在代码块中发生异常,也会正确释放锁。

Mutex 的其他方法

lock:挂起直到互斥锁被锁定。

lock() 方法用于尝试获取锁。如果锁已经被其他协程持有,那么调用 lock() 的协程将会被挂起,直到锁变为可用。

用法
import kotlinx.coroutines.*
import kotlinx.coroutines.sync.Mutex

val mutex = Mutex()

fun main() = runBlocking {
    launch {
        mutex.lock() // 获取锁
        try {
            // 保护的代码段
            println("Locked by coroutine 1")
            delay(1000)
        } finally {
            mutex.unlock() // 确保释放锁
        }
    }

    launch {
        mutex.lock() // 等待并获取锁
        try {
            // 保护的代码段
            println("Locked by coroutine 2")
        } finally {
            mutex.unlock() // 确保释放锁
        }
    }
}

unlock:解锁互斥锁。

unlock() 方法用于释放锁,使得被挂起的其他协程可以继续执行。如果 unlock() 被调用时没有持有锁,则会引发异常。

用法

如上面 lock() 示例中的 finally 块所示。

tryLock

tryLock() 尝试获取锁,如果锁当前是可用的,则立即获取锁并返回 true;否则返回 false,且不会挂起当前协程。

用法
import kotlinx.coroutines.*
import kotlinx.coroutines.sync.Mutex

val mutex = Mutex()

fun main() = runBlocking {
    launch {
        if (mutex.tryLock()) { // 尝试获取锁
            try {
                println("Lock acquired by coroutine 1")
                delay(1000)
            } finally {
                mutex.unlock()
            }
        } else {
            println("Coroutine 1: Lock not acquired")
        }
    }

    launch {
        if (mutex.tryLock()) { // 尝试获取锁
            try {
                println("Lock acquired by coroutine 2")
            } finally {
                mutex.unlock()
            }
        } else {
            println("Coroutine 2: Lock not acquired")
        }
    }
}

总结

  • lock():尝试获取锁,如果锁不可用,则挂起当前协程。
  • unlock():释放锁,其他挂起的协程可以继续执行。
  • tryLock():尝试获取锁,如果锁不可用,则立即返回 false,不会挂起当前协程。
  • withLock():便捷方法,自动获取和释放锁,确保在代码块执行后释放锁。

Mutex 的这些方法使得在 Kotlin 协程中进行线程安全的操作变得更加简洁和直观。根据实际需求选择合适的方法,可以有效避免并发问题,提高代码的健壮性和可维护性。


欢迎关注我的公众号AntDream查看更多精彩文章!

目录
相关文章
|
1月前
|
安全 Java Android开发
为什么大厂要求安卓开发者掌握Kotlin和Jetpack?深度解析现代Android开发生态优雅草卓伊凡
为什么大厂要求安卓开发者掌握Kotlin和Jetpack?深度解析现代Android开发生态优雅草卓伊凡
76 0
为什么大厂要求安卓开发者掌握Kotlin和Jetpack?深度解析现代Android开发生态优雅草卓伊凡
|
3月前
|
存储 NoSQL Redis
阿里面试:Redis 为啥那么快?怎么实现的100W并发?说出了6大架构,面试官跪地: 纯内存 + 尖端结构 + 无锁架构 + EDA架构 + 异步日志 + 集群架构
阿里面试:Redis 为啥那么快?怎么实现的100W并发?说出了6大架构,面试官跪地: 纯内存 + 尖端结构 + 无锁架构 + EDA架构 + 异步日志 + 集群架构
阿里面试:Redis 为啥那么快?怎么实现的100W并发?说出了6大架构,面试官跪地: 纯内存 + 尖端结构 +  无锁架构 +  EDA架构  + 异步日志 + 集群架构
|
5月前
|
Android开发 开发者 Kotlin
Android实战经验之Kotlin中快速实现MVI架构
MVI架构通过单向数据流和不可变状态,提供了一种清晰、可预测的状态管理方式。在Kotlin中实现MVI架构,不仅提高了代码的可维护性和可测试性,还能更好地应对复杂的UI交互和状态管理。通过本文的介绍,希望开发者能够掌握MVI架构的核心思想,并在实际项目中灵活应用。
188 8
|
6月前
|
编译器 Android开发 开发者
Android经典面试题之Kotlin中Lambda表达式和匿名函数的区别
Lambda表达式和匿名函数都是Kotlin中强大的特性,帮助开发者编写简洁而高效的代码。理解它们的区别和适用场景,有助于选择最合适的方式来解决问题。希望本文的详细讲解和示例能够帮助你在Kotlin开发中更好地运用这些特性。
100 9
|
8月前
|
缓存 NoSQL 关系型数据库
大厂面试高频:如何解决Redis缓存雪崩、缓存穿透、缓存并发等5大难题
本文详解缓存雪崩、缓存穿透、缓存并发及缓存预热等问题,提供高可用解决方案,帮助你在大厂面试和实际工作中应对这些常见并发场景。关注【mikechen的互联网架构】,10年+BAT架构经验倾囊相授。
大厂面试高频:如何解决Redis缓存雪崩、缓存穿透、缓存并发等5大难题
|
9月前
|
XML 前端开发 Android开发
Android面试高频知识点(3) 详解Android View的绘制流程
Android面试高频知识点(3) 详解Android View的绘制流程
Android面试高频知识点(3) 详解Android View的绘制流程
|
11月前
|
存储 Java
【IO面试题 四】、介绍一下Java的序列化与反序列化
Java的序列化与反序列化允许对象通过实现Serializable接口转换成字节序列并存储或传输,之后可以通过ObjectInputStream和ObjectOutputStream的方法将这些字节序列恢复成对象。
|
8月前
|
存储 算法 Java
大厂面试高频:什么是自旋锁?Java 实现自旋锁的原理?
本文详解自旋锁的概念、优缺点、使用场景及Java实现。关注【mikechen的互联网架构】,10年+BAT架构经验倾囊相授。
大厂面试高频:什么是自旋锁?Java 实现自旋锁的原理?
|
8月前
|
存储 缓存 算法
面试官:单核 CPU 支持 Java 多线程吗?为什么?被问懵了!
本文介绍了多线程环境下的几个关键概念,包括时间片、超线程、上下文切换及其影响因素,以及线程调度的两种方式——抢占式调度和协同式调度。文章还讨论了减少上下文切换次数以提高多线程程序效率的方法,如无锁并发编程、使用CAS算法等,并提出了合理的线程数量配置策略,以平衡CPU利用率和线程切换开销。
面试官:单核 CPU 支持 Java 多线程吗?为什么?被问懵了!
|
8月前
|
存储 缓存 Java
大厂面试必看!Java基本数据类型和包装类的那些坑
本文介绍了Java中的基本数据类型和包装类,包括整数类型、浮点数类型、字符类型和布尔类型。详细讲解了每种类型的特性和应用场景,并探讨了包装类的引入原因、装箱与拆箱机制以及缓存机制。最后总结了面试中常见的相关考点,帮助读者更好地理解和应对面试中的问题。
192 4

热门文章

最新文章