一、基础概念题
1. 进程和线程区别?
- 进程:资源分配最小单位,独立内存、文件句柄,进程间隔离,切换开销大。
- 线程:CPU 调度最小单位,共享进程堆 / 全局变量,独有栈 / 程序计数器,切换开销小。
- 关系:一个进程至少 1 个主线程;进程崩溃会杀死所有线程,单一线程崩溃仅终止自身。
- IPC 进程通信:管道、socket、共享内存;线程通信直接共享变量。
2. 并发与并行区别
- 并发:单核 CPU,时间片轮转,宏观同时执行,微观交替。
- 并行:多核 CPU,多个线程真正同时运行。
3. 线程的 5 种状态(Java 标准)
新建 New → 就绪 Runnable → 运行 Running → 阻塞 Blocked → 终止 Terminated 阻塞细分:
- 同步阻塞:没抢到 synchronized 锁
- 等待阻塞:调用 wait (),释放锁
- 超时阻塞:sleep (long)/wait (long)
4. sleep ()、wait ()、yield ()、join () 区别
表格
| 方法 | 所属类 | 是否释放锁 | 唤醒条件 |
|---|---|---|---|
| sleep | Thread | ❌不释放 | 时间到自动唤醒 |
| wait | Object | ✅释放锁 | notify/notifyAll/ 超时 |
| yield | Thread | ❌不释放 | 让出 CPU,立刻回到就绪 |
| join | Thread | 不释放 | 目标线程执行完毕 |
补充:
- wait 必须写在 synchronized 代码块;sleep 任意位置。
- yield 只是让出调度,不保证一定切换。
5. 为什么 wait/notify 定义在 Object 而不是 Thread?
锁是对象级别的,每个对象都有监视器锁;等待 / 唤醒是针对对象锁操作,不是线程。如果放在 Thread,一把锁对应多个线程无法实现。
二、锁与同步高频题
6. synchronized 底层原理、锁升级
- 底层:对象头 Mark Word、监视器锁 monitorenter/monitorexit 指令。
- 锁膨胀升级(不可逆): 偏向锁 → 轻量级锁(自旋) → 重量级锁(OS 内核互斥量)
- 三种用法:
- 修饰实例方法:锁当前对象 this
- 修饰静态方法:锁类 Class 对象
- 同步代码块:手动指定锁对象
7. synchronized 和 Lock(ReentrantLock)对比
- synchronized:自动释放锁、非公平、不可中断、不支持条件变量。
- ReentrantLock:手动 lock/unlock、支持公平 / 非公平锁、可中断、多 Condition、可尝试获取锁 tryLock。
- 相同:都可重入。
8. 可重入锁是什么?为什么要可重入?
同一线程多次获取同一把锁不会死锁;比如同步方法 A 调用同步方法 B,不加可重入会自我阻塞。
9. 死锁产生 4 条件 + 如何避免
四条件(缺一不可):
- 互斥 2. 持有并等待 3. 不可剥夺 4. 循环等待 避免方案:
- 统一锁获取顺序(最常用)
- 设置超时 tryLock
- 破坏持有等待,一次性获取所有锁
10. volatile 关键字作用
- 保证可见性:修改立即刷入主存,其他线程可见。
- 禁止指令重排(内存屏障)。
- ❌不保证原子性,不能替代锁做计数。 适用场景:状态标记、双重检查锁 DCL 单例。
11. volatile 为什么不能保证原子性?
i++ 分为 3 步:读 i → +1 → 写回;volatile 只能保证读写可见,中间计算会被多线程覆盖。 解决方案:AtomicInteger、synchronized、Lock。
12. Atomic 原子类原理
CAS(Compare And Swap)自旋乐观锁,底层 Unsafe 调用 CPU 指令 cmpxchg;无锁,性能高于 synchronized。 缺点:自旋空转消耗 CPU、ABA 问题。
13. ABA 问题怎么解决?
加版本号 AtomicStampedReference,每次修改版本 + 1,比较值 + 版本双重校验。
三、线程池核心面试(必问)
14. 为什么要用线程池?
- 降低创建销毁线程开销
- 控制并发数量,防止无限创建线程 OOM
- 统一管理线程、延迟 / 定时任务、复用线程
15. ThreadPoolExecutor 七大参数
- corePoolSize 核心线程数
- maximumPoolSize 最大线程数
- keepAliveTime 非核心空闲存活时间
- unit 时间单位
- workQueue 阻塞队列
- threadFactory 线程工厂(自定义线程名)
- handler 拒绝策略
16. 线程池执行流程
- 任务进来,核心线程未满 → 创建核心线程执行
- 核心满,任务放入阻塞队列
- 队列满,创建非核心线程到 maxPoolSize
- 达到最大线程,执行拒绝策略
17. 四种拒绝策略
- AbortPolicy(默认):直接抛 RejectedExecutionException
- DiscardPolicy:丢弃当前任务,无异常
- DiscardOldestPolicy:丢弃队列队首老任务,加入新任务
- CallerRunsPolicy:交给提交任务的主线程执行
18. JDK 内置 4 种线程池(Executors),阿里规范为什么禁止使用?
- newFixedThreadPool:无界队列,任务堆积 OOM
- newSingleThreadExecutor:无界队列
- newCachedThreadPool:最大线程 Integer.MAX_VALUE,线程无限创建 OOM
- newScheduledThreadPool:同样超大上限 规范:手动创建 ThreadPoolExecutor,自定义队列长度、拒绝策略。
19. 核心线程会被回收吗?
默认不会;allowCoreThreadTimeOut (true) 开启后,核心线程空闲超时也会销毁。
20. 如何合理设置线程池核心参数?
- CPU 密集型:核心线程 = CPU 核心数 + 1
- IO 密集型:核心线程 = 2 * CPU 核心数 或 核心数 /(1 - 阻塞系数)
- 业务区分:数据库、RPC、文件 IO 属于 IO 密集
四、并发工具类
21. CountDownLatch、CyclicBarrier、Semaphore 区别
- CountDownLatch:一次性,主线程等待 N 个线程完成,不可重置。
- CyclicBarrier:可循环复用,N 个线程互相等待全部到达屏障再一起执行。
- Semaphore:信号量,控制同一时间并发线程数量(限流)。
22. ThreadLocal 原理、内存泄漏
- 底层:Thread 内部 ThreadLocalMap,key 弱引用 ThreadLocal,value 强引用。
- 内存泄漏原因:Thread 长期存活(线程池),ThreadLocal 对象被回收,但 value 无法释放。
- 解决:用完调用 remove ();避免在线程池滥用。
五、手写代码面试题(手撕)
1. 两个线程交替打印 1-100(wait/notify/ Lock+Condition)
2. DCL 双重检查锁单例,volatile 作用
public class Singleton {
private volatile static Singleton instance;
private Singleton(){}
public static Singleton getInstance(){
if(instance == null){
synchronized(Singleton.class){
if(instance == null){
instance = new Singleton();
}
}
}
return instance;
}
}
volatile 禁止重排,防止半初始化对象暴露。
3. 线程安全计数器(synchronized / AtomicInteger / Lock)
4. 手写生产者消费者模型
5. 手写简易线程池(拓展题)
六、综合场景 & 项目面试题
- 接口高并发下超卖问题解决方案 分布式锁 Redisson、数据库乐观锁、队列削峰
- 线上线程池任务堆积如何排查? 打印线程栈 jstack、监控队列长度、调大核心线程 / 扩容队列
- 多线程下 HashMap 为什么死循环?JDK1.8 怎么优化? 1.7 扩容头插法链表循环;1.8 尾插 + 红黑树,无死循环,但仍线程不安全。并发用 ConcurrentHashMap。
- ConcurrentHashMap 分段锁 / 锁粒度 JDK1.7 分段 Segment;1.8 锁头节点 + CAS,粒度更细,支持并发读写。
- CompletableFuture 异步多线程编排(thenApply、allOf)
七、反问面试官(加分)
- 项目中 IO 密集还是 CPU 密集?线程池参数如何配置?
- 分布式场景下多线程如何保证数据一致性?
- 线上并发问题排查工具栈(jstack、jmap、arthas)