同步与线程安全
问题
Android 中如何保证多线程环境下的数据安全?
答案
线程安全问题来源
同步方式对比
| 方式 | 适用场景 | 性能 |
|---|---|---|
synchronized | 简单临界区 | 中等 |
ReentrantLock | 需要 tryLock、超时 | 中等 |
volatile | 单变量可见性 | 高 |
Atomic* | 无锁计算(CAS) | 高 |
Mutex(协程) | 协程中同步 | 高 |
| 线程安全集合 | 并发容器 | 中等 |
synchronized
class Counter {
private var count = 0
// 1. 方法同步
@Synchronized
fun increment() {
count++
}
// 2. 代码块同步
private val lock = Any()
fun decrement() {
synchronized(lock) {
count--
}
}
}
Atomic 原子类
class AtomicCounter {
private val count = AtomicInteger(0)
fun increment() = count.incrementAndGet()
fun get() = count.get()
// CAS 操作
fun addIfPositive(delta: Int): Boolean {
while (true) {
val current = count.get()
if (current + delta < 0) return false
if (count.compareAndSet(current, current + delta)) return true
}
}
}
协程 Mutex
class CoroutineCounter {
private val mutex = Mutex()
private var count = 0
suspend fun increment() {
mutex.withLock {
count++
}
}
// 不要在协程中使用 synchronized!
// synchronized 会阻塞整个线程,影响其他协程
}
协程中避免 synchronized
synchronized 阻塞线程(非挂起),会阻塞 Dispatcher 线程池中的线程,影响其他协程执行。协程中应使用 Mutex 或 Channel 进行同步。
线程安全集合
// 1. ConcurrentHashMap
val cache = ConcurrentHashMap<String, User>()
// 2. CopyOnWriteArrayList(读多写少)
val listeners = CopyOnWriteArrayList<Listener>()
// 3. Collections.synchronizedList
val list = Collections.synchronizedList(mutableListOf<String>())
// 4. Kotlin StateFlow / SharedFlow(协程安全)
private val _data = MutableStateFlow<List<Item>>(emptyList())
常见线程安全场景
// 单例模式(Double-Check Locking)
class Singleton private constructor() {
companion object {
@Volatile
private var instance: Singleton? = null
fun getInstance(): Singleton =
instance ?: synchronized(this) {
instance ?: Singleton().also { instance = it }
}
}
}
// 推荐:Kotlin object(天然线程安全单例)
object AppDatabase {
val db by lazy {
Room.databaseBuilder(context, AppDb::class.java, "app.db").build()
}
}
常见面试问题
Q1: volatile 有什么作用?
答案:
volatile 保证两个特性:
- 可见性:一个线程修改 volatile 变量后,其他线程立即可见(不会读到陈旧值)
- 禁止重排序:volatile 变量的读写不会与前后指令重排
但 volatile 不保证原子性。count++ 是读-改-写三步操作,即使 count 是 volatile 的,多线程 count++ 仍会出错。原子性操作需要使用 synchronized 或 AtomicInteger。
Q2: ConcurrentHashMap 是如何保证线程安全的?
答案:
JDK 8 的 ConcurrentHashMap 使用 CAS + synchronized 分段锁:
put操作:对桶头节点加synchronized锁,不同桶可以并发写入get操作:无锁,通过volatile保证可见性- 扩容:多线程协助迁移(
transfer())
相比 Hashtable(全表锁),并发性能大幅提升。
Q3: 如何选择同步方案?
答案:
- 简单变量递增:
AtomicInteger/AtomicLong - 简单临界区:
synchronized - 需要超时/可中断:
ReentrantLock - 协程中同步:
Mutex - 读多写少:
CopyOnWriteArrayList - 并发 Map:
ConcurrentHashMap - 状态管理:
StateFlow(协程安全,推荐)