Android 高级工程师面试:Java 多线程与并发 近1年高频追问 22 题

文章目录

  • 学习建议
  • 基础层(8 题)
    • #1 线程有哪些创建方式?Android 里怎么选? ⭐
      • 标准回答
      • 面试官可能继续追问
    • #2 线程有哪些状态?如何转换? ⭐
      • 标准回答
      • 面试官可能继续追问
    • #3 为什么 Android 主线程不能做耗时操作? 🔥
      • 标准回答
      • 面试官可能继续追问
    • #4 `synchronized` 的原理是什么?锁升级过程? 🔥
      • 标准回答
      • 面试官可能继续追问
    • #5 `volatile` 的语义是什么?能保证原子性吗? 🔥
      • 标准回答
      • 面试官可能继续追问
    • #6 `synchronized` 和 `volatile` 怎么选? ⭐
      • 标准回答
      • 面试官可能继续追问
    • #7 `wait/notify` 和 `sleep` 有什么区别? ⭐
      • 标准回答
      • 面试官可能继续追问
    • #8 什么是线程安全?Android 里如何保证? ⭐
      • 标准回答
      • 面试官可能继续追问
  • 进阶层(7 题)
    • #9 `ReentrantLock` 和 `synchronized` 怎么选? 🔥
      • 标准回答
      • 面试官可能继续追问
    • #10 `ThreadPoolExecutor` 七大参数是什么? 🔥
      • 标准回答
      • 面试官可能继续追问
    • #11 线程池四种拒绝策略怎么选? ⭐
      • 标准回答
      • 面试官可能继续追问
    • #12 `ConcurrentHashMap` JDK 8 实现原理? 🔥
      • 标准回答
      • 面试官可能继续追问
    • #13 `CountDownLatch` 和 `CyclicBarrier` 区别? ⭐
      • 标准回答
      • 面试官可能继续追问
    • #14 `AtomicInteger` 等原子类解决什么问题? ⭐
      • 标准回答
      • 面试官可能继续追问
    • #15 `Handler` 为什么会导致内存泄漏?如何修复? 🔥
      • 标准回答
      • 面试官可能继续追问
  • 核心层(7 题)
    • #16 `happens-before` 规则有哪些? 💡
      • 标准回答
      • 面试官可能继续追问
    • #17 AQS 是什么?`ReentrantLock` 如何依赖它? 💡
      • 标准回答
      • 面试官可能继续追问
    • #18 双重检查锁单例有什么问题?如何正确实现? 💡
      • 标准回答
      • 面试官可能继续追问
    • #19 Kotlin 协程和 Java 线程池在 Android 怎么选? 🔥
      • 标准回答
      • 面试官可能继续追问
    • #20 为什么废弃 `AsyncTask`?现代替代方案? 🔥
      • 标准回答
      • 面试官可能继续追问
    • #21 死锁如何产生?怎么排查? ⭐
      • 标准回答
      • 面试官可能继续追问
    • #22 OkHttp `Dispatcher` 线程模型是怎样的? 💡
      • 标准回答
      • 面试官可能继续追问
    • 面试策略速查
      • 必背知识(🔥)
      • 高频追问
      • 加分项(💡)
      • 容易踩坑
    • 完整链路一句通
    • 相关推荐

学习建议

目标层级建议
初级掌握基础层 8 题:主线程规则、synchronized/volatile 区别、线程状态;工程联想看追问第 1 条
中级通读全文;进阶层能讲清线程池七大参数、CHM 桶级锁、Handler 泄漏成因与修复
高级核心层加分项必答:happens-before、AQS、DCL;能串联「同步原语→线程池→协程→泄漏排查」完整链路

基础层(8 题)

#1 线程有哪些创建方式?Android 里怎么选? ⭐

标准回答

常见四种:ThreadRunnableCallable+Future线程池。裸new Thread难管控生命周期与数量,工程上应优先Executor/ThreadPoolExecutor

面试官可能继续追问

  • Android 里线程怎么选?
    网络用 OkHttp 内置 Dispatcher,图片用 Glide 线程池,业务异步优先 Kotlin 协程 +viewModelScope,避免无限创建线程导致 OOM。

  • 为什么不推荐到处new Thread
    无线程复用、无队列背压,高并发时创建销毁开销大且难统一取消。

  • 协程和线程池是什么关系?
    协程是用户态任务调度,底层常跑在线程池的少量线程上,不是 1:1 替代关系。


#2 线程有哪些状态?如何转换? ⭐

标准回答

六种:NEWRUNNABLEBLOCKEDWAITINGTIMED_WAITINGTERMINATED。等锁进BLOCKEDwait()/join()WAITINGsleep()/带超时waitTIMED_WAITING

面试官可能继续追问

  • Android 里线程状态怎么排查?
    排查 ANR 或线程卡死时,用 Profiler 或jstack看主线程是否长期BLOCKED在锁或 IO 上。

  • RUNNABLE一定在 CPU 上跑吗?
    不一定,就绪队列中也算RUNNABLE,等时间片调度。

  • 主线程BLOCKED常见原因?
    同步块里做 IO、等子线程join、或 Binder 同步调用阻塞。


#3 为什么 Android 主线程不能做耗时操作? 🔥

标准回答

主线程跑 UI 事件循环(Looper驱动MessageQueue),耗时任务占住主线程会导致输入、动画、绘制无法及时处理,出现卡顿甚至 ANR。耗时逻辑应丢到Executor、协程Dispatchers.IO或专用 HandlerThread,结果回主线程更新 UI。

面试官可能继续追问

  • ANR 触发阈值是多少?
    输入约 5s、广播 10s、前台 Service 20s(不同场景有差异)。

  • 子线程能直接update UI吗?
    不能,必须切回主线程;Compose 也遵循主线程更新状态规则。


#4synchronized的原理是什么?锁升级过程? 🔥

标准回答

synchronized基于监视器锁(Monitor),字节码层有monitorenter/monitorexit,保证互斥与可见性。JDK 偏向锁→轻量锁→重量锁逐级升级,竞争加剧时膨胀为 OS 互斥量。

面试官可能继续追问

  • Android 里synchronized怎么用?
    多线程写共享缓存、单例初始化时常用;注意锁粒度,避免在 UI 路径锁整个大对象。

  • 锁的是对象还是代码?
    锁的是对象监视器;实例方法锁this,静态方法锁Class对象。

  • ReentrantLock最大区别?
    synchronizedJVM 自动释放;ReentrantLock可中断、可超时、支持公平锁与Condition


#5volatile的语义是什么?能保证原子性吗? 🔥

标准回答

volatile保证可见性与禁止指令重排,不保证复合操作原子性(如i++)。适用单一状态标志位,如「请求已取消」「配置已刷新」。

面试官可能继续追问

  • Android 里volatile怎么用?
    DCL 单例常配合volatile防重排;多字段一致性仍用synchronized或原子类。

  • volatilesynchronized性能差多少?
    无竞争时volatile更轻;有互斥需求必须用锁或原子类。

  • DCL 单例为什么需要volatile
    new对象指令重排导致其他线程拿到未初始化完成实例。


#6synchronizedvolatile怎么选? ⭐

标准回答

要互斥改共享变量用synchronizedLock/Atomic;只发布单一可见状态用volatile

面试官可能继续追问

  • Android 里synchronizedvolatile怎么选?
    「下载取消标志」可用volatile boolean;「计数器累加」用AtomicInteger或锁,单用volatile会丢更新。

  • 多个volatile字段能保证组合原子吗?
    不能,读写之间可能被其他线程插入,需整体加锁。

  • Kotlin 里还需要手写volatile吗?
    Java 字段可用@Volatile;多数场景用协程/原子 API 更清晰。


#7wait/notifysleep有什么区别? ⭐

标准回答

wait/notify必须在同步块内调用,释放锁并进入等待队列;notify唤醒后需重新竞争锁。sleep不释放锁,仅暂停当前线程指定时间。

面试官可能继续追问

  • Android 里还用wait/notify吗?
    业务很少手写,多用CountDownLatchBlockingQueue或协程suspend;但理解有助于读 JDK/框架源码。

  • 为什么wait要在while循环里?
    防虚假唤醒,被唤醒后应再检查条件。

  • notifynotifyAll怎么选?
    不确定哪个线程满足条件时用notifyAll,避免饿死。


#8 什么是线程安全?Android 里如何保证? ⭐

标准回答

多线程并发访问下仍保持正确语义即线程安全。手段:不可变对象、线程封闭(主线程 UI)、同步锁、并发容器、原子类。

面试官可能继续追问

  • Android 里如何保证线程安全?
    RecyclerView适配器数据源若在后台改、主线程读,需 Copy-on-Write 或主线程统一调度;SparseArray非线程安全,多线程应加锁或换ConcurrentHashMap

  • StringBuilder线程安全吗?
    不安全;多线程拼接用StringBuffer或局部StringBuilder不外泄。

  • 主线程算线程安全吗?
    单线程内天然安全,但 Handler 延迟任务交叉时仍可能踩共享状态。


进阶层(7 题)

#9ReentrantLocksynchronized怎么选? 🔥

标准回答

都能互斥。ReentrantLock支持可中断、尝试锁、公平锁、Condition多条件队列,需手动unlock且最好在finally释放。synchronized语法简单、JVM 优化成熟。

面试官可能继续追问

  • Android 里ReentrantLocksynchronized怎么选?
    简单临界区用synchronized;需要超时获取或精细唤醒用ReentrantLock

  • 公平锁会降低吞吐吗?
    会,严格 FIFO 排队减少插队,高竞争下吞吐通常低于非公平锁。

  • 忘记unlock会怎样?
    其他线程永久阻塞,类似死锁;必须try-finally释放。


#10ThreadPoolExecutor七大参数是什么? 🔥

标准回答

corePoolSizemaximumPoolSizekeepAliveTimeunitworkQueuethreadFactoryhandler(拒绝策略)。任务先填满核心线程,再入队,队列满则扩到最大线程,仍满则触发拒绝策略。

面试官可能继续追问

  • Android 自建线程池注意什么?
    应显式命名threadFactory(便于 Profiler 定位),队列有界防 OOM。

  • 核心线程会回收吗?
    默认不会;allowCoreThreadTimeOut(true)可让核心线程超时回收。

  • 为什么 IO 密集和 CPU 密集池大小不同?
    CPU 密集≈核数;IO 密集可更大,因线程多在等 IO 而非占 CPU。


#11 线程池四种拒绝策略怎么选? ⭐

标准回答

AbortPolicy抛异常(默认);CallerRunsPolicy调用方线程执行,起背压;DiscardPolicy静默丢弃;DiscardOldestPolicy丢队首再提交。

面试官可能继续追问

  • Android 里拒绝策略怎么选?
    后台任务高峰时CallerRunsPolicy可减缓提交速度防雪崩;关键任务勿用Discard,应降级或持久化队列。

  • OkHttp 用哪种策略?
    Dispatcher 自定义队列与并发上限,超额任务在队列等待而非简单拒绝。

  • 无界队列有什么问题?
    任务堆积导致内存暴涨,表现为卡顿而非立刻抛异常。


#12ConcurrentHashMapJDK 8 实现原理? 🔥

标准回答

取消分段锁,采用Node数组 + 链表/红黑树,对桶头synchronized或 CAS 插入。sizeCtl控制扩容,transfer多线程协助迁移。读大多无锁(volatile保证可见)。

面试官可能继续追问

  • Android 里ConcurrentHashMap怎么用?
    多线程缓存映射可用 CHM;key 为 int 时可评估SparseArray省装箱。

  • CHM 允许null键值吗?
    不允许,防歧义;HashMap单线程场景才可以。

  • Collections.synchronizedMap区别?
    CHM 桶级锁,并发读性能远好于全表一把锁。


#13CountDownLatchCyclicBarrier区别? ⭐

标准回答

CountDownLatch一次性,计数减到零唤醒等待线程,常用于「等多路异步完成」;CyclicBarrier可重用,多线程到齐再一起走,支持屏障动作。

面试官可能继续追问

  • Android 里 Latch/Barrier 怎么用?
    启动打点、多接口聚合刷新可用 Latch;周期性同步更常用协程async/awaitAll

  • Latch 计数能加吗?
    不能,只能减;一次性语义。

  • 主线程await会 ANR 吗?
    会,主线程禁止await阻塞等待。


#14AtomicInteger等原子类解决什么问题? ⭐

标准回答

基于 CAS 无锁更新单个变量,避免synchronized重量级竞争。适合计数器、状态位、引用替换。

面试官可能继续追问

  • Android 里原子类怎么用?
    统计曝光次数、限流令牌可用原子类;复合逻辑仍需AtomicReference配合或加锁。

  • CAS 会自旋浪费 CPU 吗?
    高竞争下会;此时锁可能更合适。

  • LongAdderAtomicLong怎么选?
    高并发累加LongAdder分段降低竞争;需要精确读取当前值用AtomicLong


#15Handler为什么会导致内存泄漏?如何修复? 🔥

标准回答

非静态内部类Handler隐式持有外部ActivityMessage又持有Handler,若延迟消息未处理,Activity 无法回收。

面试官可能继续追问

  • Android 里 Handler 泄漏怎么修?
    静态内部类 +WeakReference<Activity>、页面销毁时removeCallbacksAndMessages(null)、优先lifecycleScope替代裸 Handler。

  • 主线程 Handler 也会泄漏吗?
    会,泄漏与是否主线程无关,取决于消息是否持有 Activity 引用链。

  • onDestroy里必须清消息吗?
    使用了延迟/周期性消息时必须清;纯即时消息通常随队列消化。


核心层(7 题)

#16happens-before规则有哪些? 💡

标准回答

JMM 定义的可见性偏序:程序次序、监视器锁、volatile写先于读、线程start/join传递性。理解它才能解释「为什么 DCL 要volatile」「为什么synchronized退出后其他线程能看见最新值」。

面试官可能继续追问

  • Android 里理解 happens-before 有什么用?
    读框架并发工具源码(如ConcurrentHashMapHandler同步屏障)都建立在 happens-before 之上。

  • 和「时间上的先后」是一回事吗?
    不是,happens-before 是内存可见性保证,不保证墙钟顺序。

  • final字段发布安全吗?
    正确构造后,final字段对其他线程可见,常用于不可变对象设计。


#17 AQS 是什么?ReentrantLock如何依赖它? 💡

标准回答

AbstractQueuedSynchronizerstate+ CLH 双向队列管理获取/释放同步状态。ReentrantLockSemaphoreCountDownLatch内部都基于 AQS 定制tryAcquire/tryRelease

面试官可能继续追问

  • Android 里需要了解 AQS 吗?
    虽不直接写 AQS,但读MediaCodecCamera等阻塞队列工具时有帮助。

  • state为 0 表示什么?
    锁未被持有;ReentrantLock重入时state递增。

  • 共享模式和独占模式区别?
    独占如锁;共享如Semaphore允许多个线程同时通过。


#18 双重检查锁单例有什么问题?如何正确实现? 💡

标准回答

DCL 意图减少synchronized开销,但未volatile时可能因指令重排发布半初始化对象。正确写法:volatile实例 + 同步块双重检查。

面试官可能继续追问

  • Android 里单例怎么实现更好?
    更推荐枚举单例或Application级懒加载 + 依赖注入,DCL 多见于老旧 SDK 与面试题。

  • 枚举单例为什么更好?
    JVM 保证枚举实例唯一,且防反射/反序列化破坏。

  • Kotlinobject单例需要 DCL 吗?
    不需要,语言层保证线程安全懒初始化。


#19 Kotlin 协程和 Java 线程池在 Android 怎么选? 🔥

标准回答

IO/计算任务优先协程:lifecycleScope/viewModelScope自动随生命周期取消,结构化并发避免泄漏。线程池适合 SDK 边界、Java 遗留模块、或需严格隔离线程名的场景(如 Glide)。

面试官可能继续追问

  • Android 里协程和线程池怎么选?
    不要GlobalScope;阻塞式 Java API 在协程里用withContext(Dispatchers.IO)包装,而非主线程等待。

  • 协程能替代 Handler 吗?
    延迟/切主线程可用delay+Dispatchers.Main;与Choreographer帧同步仍可能用 Handler。

  • 一个协程等于一个线程吗?
    不等于,多协程可复用少量线程,挂起时不占线程。


#20 为什么废弃AsyncTask?现代替代方案? 🔥

标准回答

AsyncTask线程池全局共享、串行/并行行为版本间不一致,易泄漏 Activity,且与生命周期脱节,API 30 起废弃。

面试官可能继续追问

  • Android 里AsyncTask替代方案?
    协程 +viewModelScopeExecutor+ 主线程回调、WorkManager做可持久后台任务。新代码禁止再引入AsyncTask

  • WorkManager和协程分工?
    需保证执行、重启、约束(网络/充电)用WorkManager;页面内短时异步用协程。

  • 老项目大量AsyncTask怎么迁移?
    按边界逐步换协程,统一在ViewModel层发状态,避免在Activity内起任务。


#21 死锁如何产生?怎么排查? ⭐

标准回答

死锁需互斥、占有且等待、不可抢占、循环等待四条件。典型:线程 A 锁m1m2,B 锁m2m1

面试官可能继续追问

  • Android 里死锁怎么排查?
    jstack/Android Studio Profiler 看Found one Java-level deadlock;预防:锁顺序一致、超时tryLock、缩小锁粒度。线上优先从主线程阻塞与 Binder 同步调用链入手。

  • 数据库也会死锁吗?
    会,Room/SQLite 事务交叉更新可能死锁,需重试或统一访问顺序。

  • tryLock失败怎么处理?
    降级、重试或上报,避免无限等待。


#22 OkHttpDispatcher线程模型是怎样的? 💡

标准回答

Dispatcher管理就绪异步调用与ExecutorService:最大 64 并发、每主机 5 并发(可配置),超额进队列等待。同步execute在调用线程执行。

面试官可能继续追问

  • Android 里 OkHttp Dispatcher 怎么理解?
    理解它可合理解释「为何大量图片请求不会无限开线程」。Glide 另有独立池,网络层不要与业务自建池混用同一无界队列。

  • 同步请求能在主线程吗?
    技术上能但会 ANR/卡顿,禁止主线程execute

  • 如何与协程suspend配合?
    suspendCancellableCoroutine包装enqueue,取消时call.cancel()


面试策略速查

面试等级建议掌握
初级★★★☆☆
中级★★★★★
高级★★★★★

必背知识(🔥)

主线程规则、synchronized/volatile、ThreadPool 七大参数、CHM、Handler 泄漏、协程 vs 线程池、AsyncTask 废弃原因

高频追问

「volatile 能保证原子吗」「线程池队列满了怎么办」「Handler 怎么防泄漏」「协程和线程区别」

加分项(💡)

happens-before、AQS 与 ReentrantLock 关系、DCL 与 volatile、OkHttp Dispatcher 并发上限

容易踩坑

主线程sleep/wait/网络请求;无界队列线程池 OOM;非静态 Handler 泄漏;用volatilei++;新代码继续用 AsyncTask


完整链路一句通

并发题一条线:主线程只管 UI 事件循环,共享状态用 synchronized/volatile/原子类/CHM 保证可见与互斥,耗时任务进线程池或协程 IO 线程,结果回主线程,页面销毁必须取消任务并清 Handler 消息,泄漏与 ANR 从这条链路上排查。


相关推荐

HashMap、mutableMapOf 与 ConcurrentHashMap 完全指南

Android 高级工程师面试:Java 基础知识 近1年高频追问 22 题