面试题答案
一键面试潜在问题
- 资源竞争:
- 分析:Kotlin的Lambda表达式和闭包可以访问外部作用域的变量。在多线程环境下,多个线程同时访问和修改这些共享变量时,就可能发生资源竞争。例如,在并发集合操作中,如果一个Lambda表达式在向集合中添加元素,同时另一个线程也在对该集合进行读取或修改操作,可能导致数据不一致。
- 示例:
import java.util.concurrent.CopyOnWriteArrayList
val sharedList = CopyOnWriteArrayList<Int>()
val threads = (1..10).map {
Thread {
(1..10).forEach {
sharedList.add(it)
}
}
}
threads.forEach { it.start() }
threads.forEach { it.join() }
println(sharedList)
在上述代码中,如果sharedList
不是线程安全的集合(如CopyOnWriteArrayList
),多个线程同时添加元素就可能导致资源竞争问题。
- 内存泄漏:
- 分析:如果Lambda表达式或闭包持有对外部对象的引用,并且该外部对象的生命周期应该比Lambda表达式短,但由于Lambda表达式被某个长时间运行的线程或对象持有,就可能导致外部对象无法被垃圾回收,从而造成内存泄漏。例如,在Android开发中,如果一个Activity中的Lambda表达式被一个后台线程持有,而Activity已经销毁,但由于Lambda表达式的引用,Activity及其相关资源无法被回收。
- 示例:
class OuterClass {
val innerLambda: () -> Unit
init {
innerLambda = {
println("This is a lambda in OuterClass")
}
}
}
fun main() {
val outer = OuterClass()
val thread = Thread {
outer.innerLambda()
}
// 如果这里没有对thread进行合适的管理,比如thread一直运行,而outer对象本应被回收,就可能造成内存泄漏
}
解决方案
- 资源竞争解决方案:
- 使用线程安全的数据结构:如上述代码中使用
CopyOnWriteArrayList
,它在每次修改操作时都会创建一个新的底层数组副本,读操作则基于旧的数组,从而保证线程安全。 - 同步机制:使用
synchronized
关键字或ReentrantLock
等同步工具。 - 示例(使用
synchronized
):
- 使用线程安全的数据结构:如上述代码中使用
val sharedList2 = mutableListOf<Int>()
val threads2 = (1..10).map {
Thread {
synchronized(sharedList2) {
(1..10).forEach {
sharedList2.add(it)
}
}
}
}
threads2.forEach { it.start() }
threads2.forEach { it.join() }
println(sharedList2)
- 内存泄漏解决方案:
- 避免长生命周期对象持有短生命周期对象的引用:在上述
OuterClass
示例中,如果thread
运行结束后能及时释放对outer
的引用,就可以避免内存泄漏。 - 使用弱引用:在Android开发中,如果Activity中的Lambda表达式需要被后台线程持有,可以使用
WeakReference
来持有Activity的引用,这样当Activity销毁时,垃圾回收器可以回收Activity及其相关资源。 - 示例(使用
WeakReference
):
- 避免长生命周期对象持有短生命周期对象的引用:在上述
class OuterClass2 {
val innerLambda: () -> Unit
init {
innerLambda = {
println("This is a lambda in OuterClass2")
}
}
}
fun main() {
val outer = OuterClass2()
val weakOuter = WeakReference(outer)
val thread2 = Thread {
val outerRef = weakOuter.get()
outerRef?.innerLambda?.invoke()
}
// 即使thread2运行过程中outer对象的生命周期结束,也不会造成内存泄漏
}