Fork me on GitHub
Hike News
Hike News

Android 面试题库:深度解析并发锁机制与应用实践

在 Android 高级开发面试中,并发锁是考察候选人底层功底、内存模型理解以及系统架构能力的核心考点。本文针对 Android 实际开发场景,系统性梳理了各种锁的原理与选型。


一、 Java 基础锁原语

1. synchronized (内置锁/监视器锁)

  • 特性:自动获取/释放、可重入、非公平。在现代 ART 虚拟机中经过了偏向锁、轻量级锁、重量级锁的膨胀优化。
  • 面试考点
    • 对象锁 vs 类锁:同步代码块与静态同步方法的区别。
    • 原理:每个对象关联的 monitor 对象(EntryList, WaitSet)。
  • 应用场景:本项目的 GLTextureView 中用于状态机的全局调度。

2. ReentrantLock (显示锁)

  • 特性:基于 AQS(AbstractQueuedSynchronizer)实现。支持公平/非公平选型、可中断、支持超时获取、多条件变量(Condition)。
  • 面试考点
    • synchronized 区别:灵活性(tryLock)、性能(高竞争下更稳定)、可扩展性。
  • 应用场景:复杂的数据结构操作,或需要响应中断的长时间等待场景。

二、 读写与性能优化锁

3. ReentrantReadWriteLock (读写锁)

  • 核心逻辑读读共享、读写互斥、写写互斥
  • 面试考点
    • 锁降级:写锁可以降级为读锁,但读锁不能升级为写锁。
    • 饥饿问题:大量读操作可能导致写操作长时间无法获取锁。
  • 应用场景:App 的内存缓存(LruCache)查询频率远高于更新频率的场景。

4. StampedLock (乐观读锁)

  • 特性:Java 8 引入。提供了一种乐观读模式,读操作不会阻塞写操作。
  • 面试考点
    • 对比 ReadWriteLock:通过版本戳(Stamp)验证数据有效性,在读多写极少的极端场景下性能近乎无锁。
  • 应用场景:坐标点数据计算、频繁读取的配置信息。

三、 原子操作与轻量级同步

5. CAS (Compare And Swap)

  • 原理:利用 CPU 的 cmpxchg 指令实现无锁原子更新。
  • 面试考点
    • ABA 问题:如何通过 AtomicStampedReference 解决。
    • 自旋开销:高竞争下 CPU 占用率过高。
  • 应用场景:计数器(AtomicInteger)、状态标记位。

6. volatile 关键字

  • 作用可见性、有序性(禁止指令重排)。不保证原子性。
  • 面试考点
    • DCL(双重检查锁定)单例模式:为什么必须加 volatile?(防止对象初始化未完成就被引用)。
  • 应用场景:本项目的 mHasContentToDraw 标志位同步。

四、 Android 场景下的特有应用

7. 渲染链路同步 (本项目核心)

  • 痛点:GL 线程与 UI 线程的生命周期不同步。
  • 方案:使用 synchronized 配合 wait/notifyAll 构建保护性暂停(Guarded Suspension)模型。
  • 面试回答:参考 GLTEXTUREVIEW_LOCKING.md 中的握手协议描述。

8. 锁的选型指南 (横向对比)

维度 synchronized ReentrantLock Atomic/CAS
灵活性 低(自动) 高(手动控制) 极高(无锁)
性能 中(低中竞争优) 高(高竞争稳定) 极高(极短操作)
功能 基础 丰富(Condition/公平性) 仅限原子变量
风险 高(易忘释放) 容易出现 ABA 问题

五、 资深面试官进阶追问

Q1:如何定位 App 线上发生的死锁?

  • :1. 使用 Thread.getAllStackTraces() 获取堆栈。2. 寻找 BLOCKED 状态的线程。3. 分析锁的循环等待链(A 等 B,B 等 A)。

Q2:为什么 ConcurrentLinkedQueue 不需要加锁?

  • :它采用了 Michael-Scott 非阻塞队列算法,底层完全基于 CAS 操作头尾指针,实现了极高的并发吞吐量。本项目在 HybridNormalRenderer 中使用它来处理弹幕队列,避免了每一帧渲染都要持锁的开销。

Q3:什么是偏向锁和锁消除?

  • :锁消除是 JIT 编译器的优化,若发现对象只会在单线程访问,则直接去掉 synchronized。偏向锁是假设锁一直由同一线程持有,减少 CAS 操作开销。

📌 总结

锁不是越多越好,而是越轻量越好。在 Android 开发中,首选无锁结构(Concurrent 容器),次选轻量级标记(volatile/Atomic),最后才考虑重权重的 Monitor 或显示锁。