文章目录
- 学习建议
- [基础层(8 题)](#基础层(8 题))
-
- [#1 线程有哪些创建方式?Android 里怎么选? ⭐](#1 线程有哪些创建方式?Android 里怎么选? ⭐)
- [#2 线程有哪些状态?如何转换? ⭐](#2 线程有哪些状态?如何转换? ⭐)
- [#3 为什么 Android 主线程不能做耗时操作? 🔥](#3 为什么 Android 主线程不能做耗时操作? 🔥)
- [#4 `synchronized` 的原理是什么?锁升级过程? 🔥](#4
synchronized的原理是什么?锁升级过程? 🔥) - [#5 `volatile` 的语义是什么?能保证原子性吗? 🔥](#5
volatile的语义是什么?能保证原子性吗? 🔥) - [#6 `synchronized` 和 `volatile` 怎么选? ⭐](#6
synchronized和volatile怎么选? ⭐) - [#7 `wait/notify` 和 `sleep` 有什么区别? ⭐](#7
wait/notify和sleep有什么区别? ⭐) - [#8 什么是线程安全?Android 里如何保证? ⭐](#8 什么是线程安全?Android 里如何保证? ⭐)
- [进阶层(7 题)](#进阶层(7 题))
-
- [#9 `ReentrantLock` 和 `synchronized` 怎么选? 🔥](#9
ReentrantLock和synchronized怎么选? 🔥) - [#10 `ThreadPoolExecutor` 七大参数是什么? 🔥](#10
ThreadPoolExecutor七大参数是什么? 🔥) - [#11 线程池四种拒绝策略怎么选? ⭐](#11 线程池四种拒绝策略怎么选? ⭐)
- [#12 `ConcurrentHashMap` JDK 8 实现原理? 🔥](#12
ConcurrentHashMapJDK 8 实现原理? 🔥) - [#13 `CountDownLatch` 和 `CyclicBarrier` 区别? ⭐](#13
CountDownLatch和CyclicBarrier区别? ⭐) - [#14 `AtomicInteger` 等原子类解决什么问题? ⭐](#14
AtomicInteger等原子类解决什么问题? ⭐) - [#15 `Handler` 为什么会导致内存泄漏?如何修复? 🔥](#15
Handler为什么会导致内存泄漏?如何修复? 🔥)
- [#9 `ReentrantLock` 和 `synchronized` 怎么选? 🔥](#9
- [核心层(7 题)](#核心层(7 题))
-
- [#16 `happens-before` 规则有哪些? 💡](#16
happens-before规则有哪些? 💡) - [#17 AQS 是什么?`ReentrantLock` 如何依赖它? 💡](#17 AQS 是什么?
ReentrantLock如何依赖它? 💡) - [#18 双重检查锁单例有什么问题?如何正确实现? 💡](#18 双重检查锁单例有什么问题?如何正确实现? 💡)
- [#19 Kotlin 协程和 Java 线程池在 Android 怎么选? 🔥](#19 Kotlin 协程和 Java 线程池在 Android 怎么选? 🔥)
- [#20 为什么废弃 `AsyncTask`?现代替代方案? 🔥](#20 为什么废弃
AsyncTask?现代替代方案? 🔥) - [#21 死锁如何产生?怎么排查? ⭐](#21 死锁如何产生?怎么排查? ⭐)
- [#22 OkHttp `Dispatcher` 线程模型是怎样的? 💡](#22 OkHttp
Dispatcher线程模型是怎样的? 💡) - 面试策略速查
- 完整链路一句通
- 相关推荐
- [#16 `happens-before` 规则有哪些? 💡](#16
学习建议
| 目标层级 | 建议 |
|---|---|
| 初级 | 掌握基础层 8 题:主线程规则、synchronized/volatile 区别、线程状态;工程联想看追问第 1 条 |
| 中级 | 通读全文;进阶层能讲清线程池七大参数、CHM 桶级锁、Handler 泄漏成因与修复 |
| 高级 | 核心层加分项必答:happens-before、AQS、DCL;能串联「同步原语→线程池→协程→泄漏排查」完整链路 |
基础层(8 题)
#1 线程有哪些创建方式?Android 里怎么选? ⭐
标准回答
常见四种:Thread、Runnable、Callable+Future、线程池。裸 new Thread 难管控生命周期与数量,工程上应优先 Executor/ThreadPoolExecutor。
面试官可能继续追问
-
Android 里线程怎么选?
网络用 OkHttp 内置 Dispatcher,图片用 Glide 线程池,业务异步优先 Kotlin 协程 +
viewModelScope,避免无限创建线程导致 OOM。 -
为什么不推荐到处
new Thread?无线程复用、无队列背压,高并发时创建销毁开销大且难统一取消。
-
协程和线程池是什么关系?
协程是用户态任务调度,底层常跑在线程池的少量线程上,不是 1:1 替代关系。
#2 线程有哪些状态?如何转换? ⭐
标准回答
六种:NEW、RUNNABLE、BLOCKED、WAITING、TIMED_WAITING、TERMINATED。等锁进 BLOCKED,wait()/join() 进 WAITING,sleep()/带超时 wait 进 TIMED_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 也遵循主线程更新状态规则。
#4 synchronized 的原理是什么?锁升级过程? 🔥
标准回答
synchronized 基于监视器锁(Monitor),字节码层有 monitorenter/monitorexit,保证互斥与可见性。JDK 偏向锁→轻量锁→重量锁逐级升级,竞争加剧时膨胀为 OS 互斥量。
面试官可能继续追问
-
Android 里
synchronized怎么用?多线程写共享缓存、单例初始化时常用;注意锁粒度,避免在 UI 路径锁整个大对象。
-
锁的是对象还是代码?
锁的是对象监视器;实例方法锁
this,静态方法锁Class对象。 -
和
ReentrantLock最大区别?synchronizedJVM 自动释放;ReentrantLock可中断、可超时、支持公平锁与Condition。
#5 volatile 的语义是什么?能保证原子性吗? 🔥
标准回答
volatile 保证可见性与禁止指令重排,不保证复合操作原子性(如 i++)。适用单一状态标志位,如「请求已取消」「配置已刷新」。
面试官可能继续追问
-
Android 里
volatile怎么用?DCL 单例常配合
volatile防重排;多字段一致性仍用synchronized或原子类。 -
volatile和synchronized性能差多少?无竞争时
volatile更轻;有互斥需求必须用锁或原子类。 -
DCL 单例为什么需要
volatile?防
new对象指令重排导致其他线程拿到未初始化完成实例。
#6 synchronized 和 volatile 怎么选? ⭐
标准回答
要互斥改共享变量用 synchronized 或 Lock/Atomic;只发布单一可见状态用 volatile。
面试官可能继续追问
-
Android 里
synchronized和volatile怎么选?「下载取消标志」可用
volatile boolean;「计数器累加」用AtomicInteger或锁,单用volatile会丢更新。 -
多个
volatile字段能保证组合原子吗?不能,读写之间可能被其他线程插入,需整体加锁。
-
Kotlin 里还需要手写
volatile吗?Java 字段可用
@Volatile;多数场景用协程/原子 API 更清晰。
#7 wait/notify 和 sleep 有什么区别? ⭐
标准回答
wait/notify 必须在同步块内调用,释放锁并进入等待队列;notify 唤醒后需重新竞争锁。sleep 不释放锁,仅暂停当前线程指定时间。
面试官可能继续追问
-
Android 里还用
wait/notify吗?业务很少手写,多用
CountDownLatch、BlockingQueue或协程suspend;但理解有助于读 JDK/框架源码。 -
为什么
wait要在while循环里?防虚假唤醒,被唤醒后应再检查条件。
-
notify和notifyAll怎么选?不确定哪个线程满足条件时用
notifyAll,避免饿死。
#8 什么是线程安全?Android 里如何保证? ⭐
标准回答
多线程并发访问下仍保持正确语义即线程安全。手段:不可变对象、线程封闭(主线程 UI)、同步锁、并发容器、原子类。
面试官可能继续追问
-
Android 里如何保证线程安全?
RecyclerView适配器数据源若在后台改、主线程读,需 Copy-on-Write 或主线程统一调度;SparseArray非线程安全,多线程应加锁或换ConcurrentHashMap。 -
StringBuilder线程安全吗?不安全;多线程拼接用
StringBuffer或局部StringBuilder不外泄。 -
主线程算线程安全吗?
单线程内天然安全,但 Handler 延迟任务交叉时仍可能踩共享状态。
进阶层(7 题)
#9 ReentrantLock 和 synchronized 怎么选? 🔥
标准回答
都能互斥。ReentrantLock 支持可中断、尝试锁、公平锁、Condition 多条件队列,需手动 unlock 且最好在 finally 释放。synchronized 语法简单、JVM 优化成熟。
面试官可能继续追问
-
Android 里
ReentrantLock和synchronized怎么选?简单临界区用
synchronized;需要超时获取或精细唤醒用ReentrantLock。 -
公平锁会降低吞吐吗?
会,严格 FIFO 排队减少插队,高竞争下吞吐通常低于非公平锁。
-
忘记
unlock会怎样?其他线程永久阻塞,类似死锁;必须
try-finally释放。
#10 ThreadPoolExecutor 七大参数是什么? 🔥
标准回答
corePoolSize、maximumPoolSize、keepAliveTime、unit、workQueue、threadFactory、handler(拒绝策略)。任务先填满核心线程,再入队,队列满则扩到最大线程,仍满则触发拒绝策略。
面试官可能继续追问
-
Android 自建线程池注意什么?
应显式命名
threadFactory(便于 Profiler 定位),队列有界防 OOM。 -
核心线程会回收吗?
默认不会;
allowCoreThreadTimeOut(true)可让核心线程超时回收。 -
为什么 IO 密集和 CPU 密集池大小不同?
CPU 密集≈核数;IO 密集可更大,因线程多在等 IO 而非占 CPU。
#11 线程池四种拒绝策略怎么选? ⭐
标准回答
AbortPolicy 抛异常(默认);CallerRunsPolicy 调用方线程执行,起背压;DiscardPolicy 静默丢弃;DiscardOldestPolicy 丢队首再提交。
面试官可能继续追问
-
Android 里拒绝策略怎么选?
后台任务高峰时
CallerRunsPolicy可减缓提交速度防雪崩;关键任务勿用Discard,应降级或持久化队列。 -
OkHttp 用哪种策略?
Dispatcher 自定义队列与并发上限,超额任务在队列等待而非简单拒绝。
-
无界队列有什么问题?
任务堆积导致内存暴涨,表现为卡顿而非立刻抛异常。
#12 ConcurrentHashMap JDK 8 实现原理? 🔥
标准回答
取消分段锁,采用 Node 数组 + 链表/红黑树,对桶头 synchronized 或 CAS 插入。sizeCtl 控制扩容,transfer 多线程协助迁移。读大多无锁(volatile 保证可见)。
面试官可能继续追问
-
Android 里
ConcurrentHashMap怎么用?多线程缓存映射可用 CHM;key 为 int 时可评估
SparseArray省装箱。 -
CHM 允许
null键值吗?不允许,防歧义;
HashMap单线程场景才可以。 -
和
Collections.synchronizedMap区别?CHM 桶级锁,并发读性能远好于全表一把锁。

#13 CountDownLatch 和 CyclicBarrier 区别? ⭐
标准回答
CountDownLatch 一次性,计数减到零唤醒等待线程,常用于「等多路异步完成」;CyclicBarrier 可重用,多线程到齐再一起走,支持屏障动作。
面试官可能继续追问
-
Android 里 Latch/Barrier 怎么用?
启动打点、多接口聚合刷新可用 Latch;周期性同步更常用协程
async/awaitAll。 -
Latch 计数能加吗?
不能,只能减;一次性语义。
-
主线程
await会 ANR 吗?会,主线程禁止
await阻塞等待。
#14 AtomicInteger 等原子类解决什么问题? ⭐
标准回答
基于 CAS 无锁更新单个变量,避免 synchronized 重量级竞争。适合计数器、状态位、引用替换。
面试官可能继续追问
-
Android 里原子类怎么用?
统计曝光次数、限流令牌可用原子类;复合逻辑仍需
AtomicReference配合或加锁。 -
CAS 会自旋浪费 CPU 吗?
高竞争下会;此时锁可能更合适。
-
LongAdder和AtomicLong怎么选?高并发累加
LongAdder分段降低竞争;需要精确读取当前值用AtomicLong。
#15 Handler 为什么会导致内存泄漏?如何修复? 🔥
标准回答
非静态内部类 Handler 隐式持有外部 Activity,Message 又持有 Handler,若延迟消息未处理,Activity 无法回收。
面试官可能继续追问
-
Android 里 Handler 泄漏怎么修?
静态内部类 +
WeakReference<Activity>、页面销毁时removeCallbacksAndMessages(null)、优先lifecycleScope替代裸 Handler。 -
主线程 Handler 也会泄漏吗?
会,泄漏与是否主线程无关,取决于消息是否持有 Activity 引用链。
-
onDestroy里必须清消息吗?使用了延迟/周期性消息时必须清;纯即时消息通常随队列消化。
核心层(7 题)
#16 happens-before 规则有哪些? 💡
标准回答
JMM 定义的可见性偏序:程序次序、监视器锁、volatile 写先于读、线程 start/join、传递性。理解它才能解释「为什么 DCL 要 volatile」「为什么 synchronized 退出后其他线程能看见最新值」。
面试官可能继续追问
-
Android 里理解 happens-before 有什么用?
读框架并发工具源码(如
ConcurrentHashMap、Handler同步屏障)都建立在 happens-before 之上。 -
和「时间上的先后」是一回事吗?
不是,happens-before 是内存可见性保证,不保证墙钟顺序。
-
final字段发布安全吗?正确构造后,
final字段对其他线程可见,常用于不可变对象设计。
#17 AQS 是什么?ReentrantLock 如何依赖它? 💡
标准回答
AbstractQueuedSynchronizer 用 state + CLH 双向队列管理获取/释放同步状态。ReentrantLock、Semaphore、CountDownLatch 内部都基于 AQS 定制 tryAcquire/tryRelease。
面试官可能继续追问
-
Android 里需要了解 AQS 吗?
虽不直接写 AQS,但读
MediaCodec、Camera等阻塞队列工具时有帮助。 -
state为 0 表示什么?锁未被持有;
ReentrantLock重入时state递增。 -
共享模式和独占模式区别?
独占如锁;共享如
Semaphore允许多个线程同时通过。
#18 双重检查锁单例有什么问题?如何正确实现? 💡
标准回答
DCL 意图减少 synchronized 开销,但未 volatile 时可能因指令重排发布半初始化对象。正确写法:volatile 实例 + 同步块双重检查。
面试官可能继续追问
-
Android 里单例怎么实现更好?
更推荐枚举单例或
Application级懒加载 + 依赖注入,DCL 多见于老旧 SDK 与面试题。 -
枚举单例为什么更好?
JVM 保证枚举实例唯一,且防反射/反序列化破坏。
-
Kotlin
object单例需要 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替代方案?协程 +
viewModelScope、Executor+ 主线程回调、WorkManager做可持久后台任务。新代码禁止再引入AsyncTask。 -
WorkManager和协程分工?需保证执行、重启、约束(网络/充电)用
WorkManager;页面内短时异步用协程。 -
老项目大量
AsyncTask怎么迁移?按边界逐步换协程,统一在
ViewModel层发状态,避免在Activity内起任务。
#21 死锁如何产生?怎么排查? ⭐
标准回答
死锁需互斥、占有且等待、不可抢占、循环等待四条件。典型:线程 A 锁 m1 等 m2,B 锁 m2 等 m1。
面试官可能继续追问
-
Android 里死锁怎么排查?
jstack/Android Studio Profiler 看Found one Java-level deadlock;预防:锁顺序一致、超时tryLock、缩小锁粒度。线上优先从主线程阻塞与 Binder 同步调用链入手。 -
数据库也会死锁吗?
会,Room/SQLite 事务交叉更新可能死锁,需重试或统一访问顺序。
-
tryLock失败怎么处理?降级、重试或上报,避免无限等待。
#22 OkHttp Dispatcher 线程模型是怎样的? 💡
标准回答
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 泄漏;用 volatile 做 i++;新代码继续用 AsyncTask
完整链路一句通
并发题一条线:主线程只管 UI 事件循环,共享状态用 synchronized/volatile/原子类/CHM 保证可见与互斥,耗时任务进线程池或协程 IO 线程,结果回主线程,页面销毁必须取消任务并清 Handler 消息,泄漏与 ANR 从这条链路上排查。