Java多线程面试8大高频考点:从线程池到死锁排查全解析

60 阅读6分钟

一、线程与进程的本质区别

  • 进程:操作系统资源分配的基本单位,每个进程有独立的内存空间(如Chrome的每个标签页是一个独立进程)。
  • 线程:CPU调度的基本单位,共享进程的资源(如内存、文件句柄)。一个进程至少包含一个线程(主线程),但线程切换开销远小于进程。

关键区别

  1. 资源开销:线程共享进程资源,创建和切换成本更低;进程独立分配资源,开销更大。
  2. 通信方式:线程间通过共享变量直接通信(如wait/notify);进程间需通过管道、消息队列等机制。
  3. 稳定性:线程崩溃可能导致整个进程崩溃;进程崩溃不影响其他进程。

面试高频题

  • “为什么Java多线程性能优于多进程?”
    答案要点:线程共享内存,减少上下文切换开销;适合高并发场景(如Web服务器)。

二、线程池的核心参数与配置策略

ThreadPoolExecutor参数详解

  1. corePoolSize:核心线程数,即使空闲也保留。
  2. maximumPoolSize:最大线程数,当队列满时触发扩容。
  3. keepAliveTime:非核心线程存活时间,任务密集时可适当调大。
  4. workQueue:任务队列(如LinkedBlockingQueue),决定任务等待策略。
  5. RejectedExecutionHandler:拒绝策略(如AbortPolicy抛出异常)。

典型场景与配置建议

  • 短时高并发任务(如秒杀系统):使用CachedThreadPool,动态扩展线程数;设置较小的keepAliveTime(如60秒),避免线程闲置浪费。
  • 长时稳定任务(如定时任务):使用ScheduledThreadPool,固定核心线程数;队列选择DelayQueue,实现延迟执行。

面试高频题

  • “为什么线程池要区分核心线程和非核心线程?”
    答案要点:核心线程保障基础吞吐量,非核心线程应对突发流量,避免资源浪费。

三、线程同步与锁机制

synchronized vs ReentrantLock

特性synchronizedReentrantLock
可中断✅(支持tryLock())
公平锁✅(可配置公平锁)
读写锁✅(ReentrantReadWriteLock)
性能低(JDK1.6后优化)高(适合复杂锁场景)

锁优化策略

  • 锁粒度:缩小锁作用范围(如只锁共享资源段),减少竞争。
  • 锁分离:读写分离(如ConcurrentHashMap分段锁),提升并发性能。
  • 锁消除:JVM自动识别无需同步的代码(如StringBuffer局部对象),优化性能。

面试高频题

  • “如何避免死锁?”
    答案要点:按固定顺序获取锁、使用tryLock()设置超时、减少锁嵌套。

四、死锁的四个必要条件与排查方法

死锁的四个必要条件

  1. 互斥:资源只能被一个线程占用。
  2. 请求与保持:线程持有资源并请求其他资源。
  3. 不可抢占:资源不能被强制剥夺。
  4. 循环等待:线程间形成资源环。

排查与解决方法

  1. 工具检测jstack:分析线程堆栈,定位死锁线程。VisualVM:可视化监控线程状态。
  2. 日志分析:记录线程获取/释放锁的顺序,发现循环依赖。
  3. 代码优化按固定顺序加锁:如所有线程先锁lockA再锁lockB。超时机制:使用tryLock()设置超时时间,避免无限等待。

面试高频题

  • “请模拟一个死锁场景并说明如何修复。”
    答案要点死锁代码:两个线程分别持有lockA和lockB,并尝试获取对方锁。修复方案:统一锁顺序,或使用tryLock()替代lock()。

五、线程间通信与协作

经典场景:生产者-消费者模型

  • 问题:生产者向缓冲区写入数据,消费者读取数据,需避免缓冲区溢出或空读。
  • 解决方案阻塞队列(如BlockingQueue):自动处理等待与唤醒逻辑。wait/notify:通过条件判断实现线程协作。

面试高频题

  • “如何实现三个线程按顺序执行(T1→T2→T3)?”
    答案要点join方法:T1执行完后调用T2.join(),T2执行完后调用T3.join()。CountDownLatch:使用计数器控制线程启动顺序。

六、volatile关键字与内存可见性

volatile的三大特性

  1. 可见性:修改后立即刷新到主内存,确保其他线程读取最新值。
  2. 禁止指令重排:通过内存屏障(Memory Barrier)防止编译器优化。
  3. 非原子性:仅保证可见性,不保证复合操作的原子性(如i++)。

适用场景

  • 状态标志:如volatile boolean flag用于线程终止信号。
  • 单写多读:如缓存更新标志。

面试高频题

  • “volatile能替代synchronized吗?”
    答案要点:不能。volatile仅解决可见性与有序性,无法保证原子性(如i++需配合CAS)。

七、Java内存模型(JMM)与happen-before规则

happen-before规则

  1. 程序顺序规则:同一个线程内,前操作happen-before后操作。
  2. 监视器锁规则:解锁happen-before加锁。
  3. volatile变量规则:写happen-before读。
  4. 线程启动规则:Thread.start()happen-before线程执行。
  5. 线程终止规则:线程执行happen-beforeThread.join()返回。

面试高频题

  • “为什么需要happen-before规则?”
    答案要点:在编译器、CPU指令重排的背景下,happen-before规则定义了操作的可见性与顺序性,避免并发错误。

八、并发工具类与高阶应用

常用工具类及场景

  1. CountDownLatch:倒计时锁,用于等待多个线程完成。场景:主线程等待所有子线程执行完毕再继续。
  2. CyclicBarrier:循环屏障,用于多线程协同启动。场景:多个线程准备就绪后同时开始计算。
  3. Semaphore:信号量,用于限制资源访问数量。场景:数据库连接池控制最大连接数。
  4. Phaser:灵活阶段划分,适用于动态线程协作。场景:分布式任务分阶段执行。

面试高频题

  • “如何用Semaphore实现限流?”
    答案要点:初始化Semaphore(10),每个请求调用acquire()获取许可,释放后调用release()。

九、多线程开发的常见陷阱

  1. 线程泄漏原因:线程池未关闭或任务未正确终止。解决方案:使用ExecutorService.shutdown()优雅关闭线程池。
  2. 上下文丢失原因:线程间传递数据未使用ThreadLocal。解决方案:在异步任务中传递ThreadLocal变量或使用TransmittableThreadLocal。
  3. 资源竞争原因:多线程同时修改共享资源。解决方案:使用原子类(如AtomicInteger)或锁机制。

面试高频题

  • “如何优化高并发下的性能瓶颈?”
    答案要点锁优化:减少锁粒度,使用CAS无锁算法。异步化:将耗时操作异步执行(如使用CompletableFuture)。缓存:通过本地缓存(如Guava Cache)减少数据库访问。

高频考点记忆口诀

  • 线程池:核心队列与拒绝,参数配置需谨慎。
  • 死锁排查:互斥保持不可抢,循环等待是根源。
  • 同步机制:synchronized粗粒度,ReentrantLock更灵活。
  • 内存模型:volatile见可见,happen-before保顺序。