面试题答案
一键面试可能出现的线程安全问题
- 竞争条件(Race Condition):
- 举例:假设有一个属性委托用于懒加载一个资源,在多线程环境下,多个线程可能同时检查该资源是否已初始化。如果都判断为未初始化,就会导致多次初始化。例如:
class Resource {
// 模拟资源初始化
init {
println("Resource initialized")
}
}
class MyClass {
private var _resource: Resource? = null
val resource: Resource by lazy {
_resource ?: Resource().also { _resource = it }
}
}
- 在多线程环境下,不同线程调用
myClass.resource
时,可能会多次执行Resource()
初始化代码,造成资源的重复初始化。
- 数据不一致:
- 举例:当属性委托涉及到对共享数据的读写操作时,不同线程的读写顺序可能导致数据不一致。例如,一个属性委托用于统计某个事件发生的次数:
class Counter {
private var count = 0
val eventCount: Int by Delegates.observable(0) { _, _, new ->
count = new
}
fun increment() {
eventCount++
}
}
- 在多线程环境下,多个线程同时调用
increment()
方法,由于线程调度的不确定性,可能导致count
的值与预期不符,出现数据不一致的情况。
解决方法
- 使用
synchronized
关键字:- 针对竞争条件:对属性委托的初始化部分进行同步。例如,修改上述懒加载资源的代码:
class Resource {
init {
println("Resource initialized")
}
}
class MyClass {
private var _resource: Resource? = null
val resource: Resource by lazy {
synchronized(this) {
_resource ?: Resource().also { _resource = it }
}
}
}
- 针对数据不一致:对涉及共享数据读写的方法进行同步。例如,修改计数器代码:
class Counter {
private var count = 0
val eventCount: Int by Delegates.observable(0) { _, _, new ->
count = new
}
fun increment() {
synchronized(this) {
eventCount++
}
}
}
- 使用线程安全的数据结构:
- 举例:如果属性委托涉及到集合操作,可以使用线程安全的集合。例如,使用
ConcurrentHashMap
代替普通的HashMap
。假设属性委托用于管理一些键值对数据:
- 举例:如果属性委托涉及到集合操作,可以使用线程安全的集合。例如,使用
import java.util.concurrent.ConcurrentHashMap
class DataManager {
private val dataMap = ConcurrentHashMap<String, Any>()
val data: Map<String, Any> by Delegates.observable(emptyMap()) { _, _, new ->
dataMap.putAll(new as Map<String, Any>)
}
fun addData(key: String, value: Any) {
dataMap[key] = value
}
}
- 使用
Atomic
类:- 针对简单数据类型的计数等操作:例如,对于上述计数器,可以使用
AtomicInteger
。
- 针对简单数据类型的计数等操作:例如,对于上述计数器,可以使用
import java.util.concurrent.atomic.AtomicInteger
class Counter {
private val count = AtomicInteger(0)
val eventCount: Int by Delegates.observable(count.get()) { _, _, new ->
count.set(new)
}
fun increment() {
count.incrementAndGet()
}
}
这样,AtomicInteger
的原子操作保证了多线程环境下数据的一致性和线程安全。