1. 互联网大厂Java面试实战:多线程与线程池深度解析
最近帮几位准备面试的朋友做模拟面试,发现多线程和线程池相关的问题几乎成了Java技术岗的必考题。特别是大厂面试,这三个环节下来要是没被问到线程安全问题,那都不算完整的Java技术面。今天我就结合自己当年面试和被面试的经验,拆解下这类问题的典型考察点和应对策略。
2. 多线程基础与线程安全
2.1 线程生命周期与状态转换
Java线程的6种状态(NEW、RUNNABLE、BLOCKED、WAITING、TIMED_WAITING、TERMINATED)是必问基础。面试官常会让画状态转换图,重点考察这几个场景:
- 调用start()后进入RUNNABLE
- 获取锁失败进入BLOCKED
- wait()后进入WAITING
- sleep()进入TIMED_WAITING
特别注意:很多人会混淆BLOCKED和WAITING状态。BLOCKED是等待获取监视器锁(比如synchronized),而WAITING是主动调用wait()进入等待。
2.2 线程安全三大问题
面试中常要求举例说明原子性、可见性、有序性问题:
- 原子性案例:i++操作实际上包含读取、计算、写入三个步骤
- 可见性案例:一个线程修改了共享变量,另一个线程看不到最新值
- 有序性案例:指令重排导致代码执行顺序与预期不符
解决方案要对应:
- 原子性:synchronized、Lock、AtomicXXX
- 可见性:volatile、final
- 有序性:happens-before原则
3. 线程池核心原理
3.1 线程池七大参数
大厂面试必问线程池构造参数,建议用这个记忆法:
- 核心数(corePoolSize):正式工编制
- 最大数(maximumPoolSize):包括临时工的总人数
- 存活时间(keepAliveTime):临时工空闲多久被辞退
- 单位(unit):时间单位
- 工作队列(workQueue):待处理任务排队区
- 线程工厂(threadFactory):如何创建新线程
- 拒绝策略(handler):人满为患时的处理方式
3.2 四种拒绝策略对比
| 策略 | 行为 | 适用场景 |
|---|---|---|
| AbortPolicy | 直接抛出RejectedExecutionException | 需要明确知道任务被拒绝 |
| CallerRunsPolicy | 由调用者线程执行该任务 | 不希望丢失任务 |
| DiscardPolicy | 直接丢弃不处理 | 允许丢失部分任务 |
| DiscardOldestPolicy | 丢弃队列最老的任务 | 允许丢弃旧任务 |
4. 高频面试题精讲
4.1 线程池大小如何设置
经典的线程池大小公式:
code复制线程数 = CPU核心数 * CPU利用率 * (1 + 等待时间/计算时间)
但实际面试时要结合业务场景:
- CPU密集型:N+1(N为CPU核心数)
- IO密集型:2N 或按上述公式计算
- 混合型:拆分为不同线程池
4.2 线程池执行流程
建议用流程图描述,关键节点:
- 提交任务
- 核心线程未满?创建新线程
- 核心线程已满?入队
- 队列满?创建临时线程
- 达到最大线程数?执行拒绝策略
5. 实战案例分析
5.1 订单超时取消实现
典型的多线程应用场景,实现要点:
java复制// 使用ScheduledThreadPoolExecutor
ScheduledExecutorService executor = Executors.newScheduledThreadPool(4);
executor.schedule(() -> {
if(order.getStatus() == UNPAID) {
order.cancel();
}
}, 30, TimeUnit.MINUTES);
5.2 并发计数器优化
对比不同实现方式的性能:
- synchronized方法:简单但性能差
- ReentrantLock:更灵活
- AtomicLong:最优选择
- LongAdder:高并发场景最佳
6. 面试避坑指南
6.1 常见误区
- 认为volatile能保证原子性(实际只能保证可见性)
- 混淆synchronized和ReentrantLock的使用场景
- 线程池参数设置不合理(特别是队列长度)
- 忽视ThreadLocal的内存泄漏问题
6.2 加分项准备
- 能说出AQS实现原理
- 了解ForkJoinPool工作窃取机制
- 熟悉CompletableFuture的链式调用
- 能分析JUC包中常见类的源码
7. 模拟面试实录
7.1 一面问题示例
Q:说说synchronized的实现原理?
A:从对象头Mark Word讲到monitor机制,最后提到JDK6后的锁升级过程(偏向锁->轻量级锁->重量级锁)
7.2 二面问题示例
Q:如何实现一个生产者消费者模型?
A:建议准备三种实现方式:
- wait()/notify()版本
- BlockingQueue版本
- Lock/Condition版本
7.3 三面问题示例
Q:线上服务出现线程死锁如何排查?
A:分步骤说明:
- jps查找Java进程
- jstack获取线程dump
- 分析死锁信息
- 结合业务代码定位问题
8. 学习路线建议
对于想系统掌握多线程的同学,建议按这个顺序:
- 先理解线程基础概念
- 掌握synchronized和volatile
- 学习JUC包常用类
- 研究线程池实现原理
- 最后看AQS等底层机制
最好的学习方式是边看源码边写demo,比如自己实现一个简化版的线程池。我在准备面试时就把ThreadPoolExecutor的execute()方法手写了一遍,对理解工作流程帮助很大。
