跳到主要内容

同步与线程安全

问题

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 线程池中的线程,影响其他协程执行。协程中应使用 MutexChannel 进行同步。

线程安全集合

// 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 保证两个特性:

  1. 可见性:一个线程修改 volatile 变量后,其他线程立即可见(不会读到陈旧值)
  2. 禁止重排序:volatile 变量的读写不会与前后指令重排

但 volatile 不保证原子性count++ 是读-改-写三步操作,即使 count 是 volatile 的,多线程 count++ 仍会出错。原子性操作需要使用 synchronizedAtomicInteger

Q2: ConcurrentHashMap 是如何保证线程安全的?

答案

JDK 8 的 ConcurrentHashMap 使用 CAS + synchronized 分段锁:

  • put 操作:对桶头节点加 synchronized 锁,不同桶可以并发写入
  • get 操作:无锁,通过 volatile 保证可见性
  • 扩容:多线程协助迁移(transfer()

相比 Hashtable(全表锁),并发性能大幅提升。

Q3: 如何选择同步方案?

答案

  • 简单变量递增AtomicInteger / AtomicLong
  • 简单临界区synchronized
  • 需要超时/可中断ReentrantLock
  • 协程中同步Mutex
  • 读多写少CopyOnWriteArrayList
  • 并发 MapConcurrentHashMap
  • 状态管理StateFlow(协程安全,推荐)

相关链接