【Android 面试真题】B站面经高频 11 问深度解析:从 CAS 到协程粘性,带你通关底层原理
摘要:本文深度拆解 Android 面试中的高频考点,涵盖 CAS 硬件原语、synchronized 锁升级、JVM/ART 垃圾回收演进、Jetpack 核心机制以及 Kotlin 语法底层实现。
一、 并发编程:从硬件到内核
1. CAS (Compare And Swap) 无锁战神
-
底层原语:在 Java 中通过
Unsafe类调用 C++,最终映射为 CPU 指令cmpxchg。在多核环境下,该指令会锁定北桥信号/缓存行,确保操作原子性。 -
三大死穴与解药:
-
ABA 问题:值变了又变回原样。解法:
AtomicStampedReference(加版本号)。 -
自旋性能:高并发下空转费 CPU。解法:适时升级为
synchronized挂起线程。 -
单变量限制:只能保证一个变量。解法:
AtomicReference封装对象。
-
2. 性能对决:AtomicLong vs LongAdder
-
AtomicLong:单点 CAS。所有线程死磕同一个变量,并发一高,自旋次数指数级增长。
-
LongAdder:分段累加(Cell 数组)。借鉴了
ConcurrentHashMap的分段思想,将压力分散到多个格子里。读取时通过sum()汇总。 -
结论:低并发选
AtomicLong(简单、实时);高并发选LongAdder(吞吐量巨大)。
3. synchronized 的“上位”之路(锁升级)
JVM 引入锁升级是为了避免直接调用操作系统的 Mutex(重量级锁开销极大):
-
偏向锁:只有你一个人来,我在你额头(对象头 ThreadID)打个标,下次你直接进。
-
轻量级锁:又来一个竞争者。通过 CAS 抢夺,抢不到就自旋。
-
重量级锁:人太多了或自旋太久。直接挂起线程,交给内核管理。
-
冷知识:锁只能升级不能降级,是为了减少 JVM 检查撤销锁的性能损耗。
二、 内存管理:JVM 与 ART 的艺术
1. 垃圾回收器的演进
-
JVM 代表作:
-
CMS:标记-清除算法。首个并发回收器,追求低停顿,但容易产生内存碎片。
-
G1:将内存切成 Region,Garbage First,优先回收垃圾最多的块。
-
-
Android ART 深度优化:
-
Concurrent Copying (CC):Android 8.0+ 默认。在后台并发移动存活对象,彻底根治碎片。
-
Generational CC:Android 10+。重点攻击“短命对象”,让 GC 变得更轻量、更无感。
-
三、 Jetpack 核心机制剖析
1. ViewModel:数据如何“长命百岁”?
-
生命周期:独立于
Activity。配置变更(屏幕旋转)时,Activity被销毁重建,但ViewModel存储在ViewModelStore中,通过NonConfigurationInstances机制得以幸存。 -
避坑指南:严禁持有 View 或 Context 的强引用。需要 Context 请用
AndroidViewModel提供的Application级别上下文。
2. 粘性(Sticky)事件:LiveData 与 StateFlow
-
LiveData 粘性:新订阅者能收到旧值。原理:内部维护
mVersion(全局版本)和mLastVersion(观察者版本)。只要全局版本大,就“补发”数据。 -
StateFlow 粘性:本质是
replay = 1的流。原理:collect开启时,代码第一行就是emit(value)内存当前值。 -
区别:LiveData 靠版本号补发;StateFlow 靠内存重播,且无法彻底关闭粘性。
四、 Kotlin 语法底层“脱糖”
1. 扩展函数:静态的幻觉
-
本质:编译后是
public static的静态方法。第一个参数就是被扩展的对象(this)。 -
限制:无法访问
private成员。因为它终究是类外部的“外人”,受字节码访问权限限制。
2. lateinit vs by lazy
-
lateinit:手动赋值,必须是
var。底层只是取消了非空检查,运行期若未初始化则抛出UninitializedPropertyAccessException。 -
by lazy:晚加载,必须是
val。基于委托机制,默认开启 DCL(双重检查锁),确保线程安全且只初始化一次。
3. 泛型:擦除后的遗珠
-
原理:JVM 是伪泛型,编译后
T变成Object。 -
型变口诀:PECS (Producer Extends, Consumer Super) $\rightarrow$ Kotlin 版:
out协变(生产者),in逆变(消费者)。 -
实化
reified:配合inline,将调用方的具体类型直接写入字节码,突破类型擦除的限制。
五、 Activity 交互:生命周期的回旋镖
场景:A 页面打开 B 页面
-
A.onPause():A 停止动画,保存数据。
-
B.onCreate() $\rightarrow$ onStart() $\rightarrow$ onResume():B 完成初始化并可见。
-
A.onStop():B 完全遮挡 A 后,A 才进入后台状态。
面试加分点:如果 B 是一个 Dialog 样式的 Activity,A 只会执行
onPause()而不会执行onStop(),因为 A 依然部分可见。
结语
底层原理不是用来背的,而是用来解释“为什么这么写”的。掌握了 synchronized 的锁升级,你就能理解性能优化的边界;掌握了 LiveData 的版本号,你就能处理诡异的 UI 重复刷新问题。
作者:Li Caijun
致力于分享最硬核的 Android 底层干货!
更多推荐




所有评论(0)