【Java并发编程】高频实战:死锁排查、线程安全问题定位、线程dump分析(附《思维导图》+《面试高频考点清单》)

简介: 本文系统梳理Java并发编程高频实战知识:涵盖死锁排查(Coffman四条件、jstack/Arthas分析)、线程安全定位(竞态/可见性/有序性问题及原子类、Lock、ThreadLocal等方案)与线程Dump深度解析(状态识别、死锁/锁竞争/死循环模式)。

思维导图

Java并发编程高频实战知识体系:死锁排查、线程安全定位与线程Dump分析

一、核心基础理论层

1.1 并发编程核心概念

  • 进程与线程:进程是资源分配单位,线程是CPU调度单位;Java线程与操作系统内核线程的映射关系(1:1模型)
  • 并发与并行:并发是多个任务交替执行,并行是多个任务同时执行
  • 上下文切换:CPU从一个线程切换到另一个线程的过程,涉及寄存器保存与恢复、内核态/用户态切换
  • Java内存模型(JMM):定义了线程与主内存之间的抽象关系,规定了可见性、原子性、有序性三大特性
  • happens-before原则:判断数据是否存在竞争、线程是否安全的核心依据

1.2 线程安全基础

  • 线程安全定义:多线程环境下,无论如何调度,都能得到正确结果
  • 线程不安全的根本原因
    1. 共享可变状态
    2. 竞态条件(Race Condition)
    3. 指令重排序
    4. 内存可见性问题
  • 线程安全的实现方式
    • 不可变对象
    • 线程封闭(栈封闭、ThreadLocal)
    • 同步机制(synchronized、Lock)
    • 原子类(Atomic系列)
    • 并发容器

二、死锁排查实战体系

2.1 死锁理论基础

  • 死锁定义:两个或多个线程互相等待对方释放资源,导致永久阻塞的状态
  • 死锁产生的四个必要条件( Coffman条件)
    1. 互斥条件:资源同一时间只能被一个线程持有
    2. 请求与保持条件:线程已持有至少一个资源,又请求其他被持有的资源
    3. 不可剥夺条件:资源不能被强制剥夺,只能由持有者主动释放
    4. 循环等待条件:线程之间形成循环等待资源的关系
  • 死锁的危害
    • 系统吞吐量急剧下降
    • 服务无响应
    • 资源耗尽
    • 系统崩溃

2.2 死锁的常见场景

  • 嵌套锁顺序不一致:最常见的死锁场景
    // 线程1
    synchronized(lockA) {
         
        synchronized(lockB) {
          ... }
    }
    // 线程2
    synchronized(lockB) {
         
        synchronized(lockA) {
          ... }
    }
    
  • 数据库死锁:多个事务同时更新多行数据且顺序不同
  • 分布式死锁:跨进程/跨服务的资源竞争
  • 资源池耗尽导致的类死锁:如线程池、数据库连接池耗尽
  • Thread.join()死锁:线程互相等待对方结束
  • Lock接口的错误使用:未在finally中释放锁

2.3 死锁排查工具链

2.3.1 JDK内置工具

  • jps:查看Java进程ID
    jps -l  # 列出所有Java进程及其主类全名
    
  • jstack:生成线程快照,最常用的死锁排查工具
    jstack <pid>  # 生成线程dump
    jstack -F <pid>  # 强制生成dump(当进程无响应时)
    jstack -l <pid>  # 包含锁信息
    
  • jconsole:图形化监控工具,可查看线程状态、检测死锁
  • jvisualvm:功能更强大的可视化工具,集成了jstack、jconsole等功能
  • jcmd:多功能命令行工具,可替代jps、jstack等
    jcmd <pid> Thread.print  # 生成线程dump
    

2.3.2 第三方工具

  • Arthas:阿里巴巴开源的Java诊断工具,功能强大
    thread  # 查看线程堆栈
    thread -b  # 找出阻塞其他线程的线程
    deadlock  # 检测死锁
    
  • FastThread:在线线程dump分析工具
  • IBM Thread and Monitor Dump Analyzer (TMDA):IBM提供的分析工具
  • PerfMa:国内专业的JVM性能分析平台

2.4 死锁排查步骤

  1. 确认死锁现象

    • 系统无响应,CPU使用率低但负载高
    • 关键业务操作超时
    • 日志中出现大量线程阻塞信息
  2. 获取线程dump

    • 生产环境建议使用jstack -l <pid> > dump.txt
    • 建议连续获取3-5次dump,间隔10-20秒,便于对比分析
  3. 分析线程dump

    • 查找"Found one Java-level deadlock:"字样
    • 查看每个死锁线程的堆栈信息
    • 确定互相等待的锁对象
    • 找到持有锁的线程及其堆栈
  4. 定位代码问题

    • 根据堆栈信息找到对应的代码行
    • 分析锁的获取顺序
    • 确认是否满足死锁的四个必要条件
  5. 验证与复现

    • 在测试环境复现问题
    • 验证修复方案的有效性

2.5 死锁解决方案

  • 破坏循环等待条件最常用的方法
    • 统一锁的获取顺序
    • 使用数字编号对锁进行排序
  • 破坏请求与保持条件
    • 一次性申请所有需要的资源
    • 申请不到时释放已持有的资源
  • 破坏不可剥夺条件
    • 使用tryLock(long timeout, TimeUnit unit)方法
    • 超时自动释放已持有的锁
  • 破坏互斥条件
    • 使用原子类代替锁
    • 使用读写锁(ReentrantReadWriteLock)提高并发性
  • 其他预防措施
    • 避免嵌套锁
    • 减少锁的持有时间
    • 使用开放调用(在调用外部方法时释放锁)
    • 避免在一个锁中持有多个资源

三、线程安全问题定位实战体系

3.1 常见线程安全问题类型

3.1.1 竞态条件问题

  • check-then-act:先检查后执行,如单例模式的双重检查锁定(DCL)问题
  • read-modify-write:读-改-写操作,如count++
  • 复合操作:多个原子操作组合在一起,但整体不是原子的

3.1.2 可见性问题

  • 一个线程修改了共享变量,但其他线程看不到修改结果
  • 典型场景:
    • 没有使用volatile关键字
    • 没有使用同步机制
    • 指令重排序导致的可见性问题

3.1.3 有序性问题

  • 编译器和CPU为了优化性能,会对指令进行重排序
  • 典型场景:
    • DCL单例模式的指令重排序问题
    • 没有正确使用volatile关键字
    • 没有正确使用happens-before原则

3.1.4 其他常见问题

  • 对象逸出:对象在构造完成前就被发布
  • 死循环:多线程环境下的死循环
  • 线程泄漏:线程没有正确结束,导致线程数不断增加
  • 数据不一致:多个线程同时修改同一数据导致数据错误

3.2 线程安全问题定位工具

3.2.1 静态代码分析工具

  • FindBugs/SpotBugs:检测常见的并发问题
  • PMD:代码质量分析工具,包含并发规则
  • SonarQube:代码质量管理平台,集成了多种静态分析工具
  • IntelliJ IDEA内置分析器:实时检测并发问题

3.2.2 动态分析工具

  • jstack:查看线程状态和堆栈信息
  • jconsole/jvisualvm:监控线程状态、CPU使用率、内存使用情况
  • Arthas
    watch  # 观察方法调用的参数、返回值和异常
    trace  # 跟踪方法内部调用路径和耗时
    stack  # 输出方法的调用堆栈
    
  • BTrace:动态追踪工具,可在不重启应用的情况下插入代码
  • Java Flight Recorder (JFR):JDK内置的低开销性能分析工具
  • AsyncProfiler:高性能的CPU和内存分析工具

3.2.3 测试工具

  • JUnit + TestNG:编写并发测试用例
  • OpenJDK jcstress:专门用于测试Java并发代码正确性的工具
  • Google ThreadSanitizer:检测数据竞争的工具
  • MultiThreadedTC:用于测试并发代码的框架

3.3 线程安全问题定位步骤

  1. 复现问题

    • 在测试环境尽可能复现问题
    • 增加并发压力,提高问题出现的概率
    • 记录问题出现的条件和现象
  2. 收集信息

    • 查看应用日志,寻找异常信息
    • 生成线程dump,分析线程状态
    • 监控系统资源使用情况(CPU、内存、IO)
    • 使用动态分析工具收集运行时数据
  3. 分析问题

    • 确定问题类型(竞态条件、可见性、有序性等)
    • 找到共享可变状态
    • 分析线程之间的交互关系
    • 定位问题代码
  4. 验证问题

    • 编写单元测试复现问题
    • 使用并发测试工具验证问题
    • 确认问题的根本原因
  5. 修复问题

    • 选择合适的解决方案
    • 修复代码
    • 验证修复效果

3.4 线程安全问题解决方案

  • 使用同步机制
    • synchronized关键字:方法级、代码块级
    • java.util.concurrent.locks包:ReentrantLock、ReentrantReadWriteLock、StampedLock
  • 使用原子类
    • java.util.concurrent.atomic包:AtomicInteger、AtomicLong、AtomicReference等
    • 基于CAS操作实现,无锁并发
  • 使用并发容器
    • ConcurrentHashMap代替HashMap
    • CopyOnWriteArrayList代替ArrayList
    • BlockingQueue实现生产者-消费者模式
  • 使用线程封闭
    • 栈封闭:使用局部变量
    • ThreadLocal:每个线程拥有自己的变量副本
  • 使用不可变对象
    • 将类声明为final
    • 将所有字段声明为private final
    • 不提供修改对象状态的方法
  • 正确使用volatile关键字
    • 保证可见性
    • 禁止指令重排序
    • 适用于一写多读的场景

四、线程Dump分析实战体系

4.1 线程Dump基础

  • 线程Dump定义:JVM在某一时刻所有线程的状态快照
  • 线程Dump包含的信息

    • 线程名称、ID、优先级
    • 线程状态(NEW、RUNNABLE、BLOCKED、WAITING、TIMED_WAITING、TERMINATED)
    • 线程调用堆栈
    • 锁信息(持有哪些锁、等待哪些锁)
    • 死锁信息
  • 线程状态详解
    | 状态 | 说明 |
    |------|------|
    | NEW | 线程已创建但未启动 |
    | RUNNABLE | 线程正在JVM中执行,或等待操作系统资源(如CPU) |
    | BLOCKED | 线程阻塞,等待获取监视器锁 |
    | WAITING | 线程无限期等待另一个线程执行特定操作 |
    | TIMED_WAITING | 线程在指定时间内等待另一个线程执行特定操作 |
    | TERMINATED | 线程已执行完毕 |

4.2 线程Dump生成方法

4.2.1 命令行方式

  • jstack:最常用的方法
    jstack <pid> > thread_dump_$(date +%Y%m%d_%H%M%S).txt
    
  • jcmd:推荐使用的方法
    jcmd <pid> Thread.print > thread_dump.txt
    
  • kill命令(Linux/Unix):
    kill -3 <pid>  # 生成线程dump并输出到标准输出
    

4.2.2 图形化工具方式

  • jvisualvm:在"线程"标签页点击"线程Dump"按钮
  • jconsole:在"线程"标签页点击"检测死锁"按钮
  • Arthas
    thread > thread_dump.txt
    

4.2.3 代码方式

  • 使用ThreadMXBean
    ThreadMXBean threadBean = ManagementFactory.getThreadMXBean();
    ThreadInfo[] threadInfos = threadBean.dumpAllThreads(true, true);
    for (ThreadInfo info : threadInfos) {
         
        System.out.println(info);
    }
    

4.3 线程Dump分析步骤

  1. 获取多个线程dump

    • 连续获取3-5次dump,间隔10-20秒
    • 便于对比分析线程状态变化
  2. 整体概览

    • 查看总线程数
    • 统计各状态线程的数量
    • 查看是否有死锁信息
  3. 重点关注异常线程

    • BLOCKED状态线程:数量过多可能存在锁竞争
    • WAITING/TIMED_WAITING状态线程:数量过多可能存在资源等待问题
    • RUNNABLE状态线程:长时间处于RUNNABLE状态可能存在死循环
  4. 分析锁信息

    • 查看哪些线程持有锁
    • 查看哪些线程在等待锁
    • 分析锁竞争的原因
  5. 分析调用堆栈

    • 找到问题线程的调用堆栈
    • 定位到具体的代码行
    • 分析代码逻辑
  6. 对比多个dump

    • 查看线程状态是否有变化
    • 查看锁持有情况是否有变化
    • 确认是否存在永久阻塞的线程

4.4 常见问题模式分析

4.4.1 死锁模式

  • 特征:

    • 线程dump开头有"Found one Java-level deadlock:"字样
    • 多个线程互相等待对方释放锁
    • 线程状态为BLOCKED
  • 示例:

    Found one Java-level deadlock:
    =============================
    "Thread-1":
      waiting to lock monitor 0x000000001b89c388 (object 0x00000000d600f480, a java.lang.Object),
      which is held by "Thread-0"
    "Thread-0":
      waiting to lock monitor 0x000000001b899c88 (object 0x00000000d600f490, a java.lang.Object),
      which is held by "Thread-1"
    

4.4.2 锁竞争模式

  • 特征:

    • 大量线程处于BLOCKED状态
    • 这些线程都在等待同一个锁
    • 持有锁的线程执行时间过长
  • 常见原因:

    • 锁的粒度太大
    • 锁持有时间过长
    • 锁竞争激烈

4.4.3 死循环模式

  • 特征:

    • 某个线程长时间处于RUNNABLE状态
    • CPU使用率很高
    • 线程堆栈停留在同一个方法调用
  • 常见原因:

    • 多线程环境下的死循环
    • 无限递归
    • 网络IO超时设置不当

4.4.4 资源等待模式

  • 特征:

    • 大量线程处于WAITING或TIMED_WAITING状态
    • 线程都在等待同一个资源
    • 系统吞吐量下降
  • 常见原因:

    • 数据库连接池耗尽
    • 线程池耗尽
    • 消息队列阻塞
    • 网络IO阻塞

4.5 线程Dump分析最佳实践

  • 生成多个dump:单个dump可能无法反映真实情况
  • 在问题发生时生成dump:避免在系统正常时生成
  • 保留完整的dump信息:包括锁信息和堆栈信息
  • 使用专业的分析工具:提高分析效率
  • 结合其他监控数据:如CPU使用率、内存使用情况、GC日志等
  • 建立线程dump分析知识库:记录常见问题和解决方案

五、高级进阶与最佳实践

5.1 并发编程最佳实践

  • 优先使用并发工具类:如java.util.concurrent包中的类
  • 优先使用线程池:避免手动创建线程
  • 最小化锁的范围:只在必要的代码块上加锁
  • 优先使用读写锁:在读多写少的场景下提高并发性
  • 正确使用volatile关键字:只在一写多读的场景下使用
  • 避免使用ThreadLocal存储可变状态:防止内存泄漏
  • 正确处理中断:不要忽略InterruptedException
  • 使用不可变对象:从根本上避免线程安全问题
  • 编写并发测试用例:提前发现并发问题
  • 监控并发指标:如线程数、锁竞争次数、死锁次数等

5.2 生产环境并发问题应急处理

  • 快速定位问题:使用jstack、Arthas等工具快速生成线程dump
  • 临时解决方案
    • 重启应用(最快速但会丢失数据)
    • 调整线程池大小
    • 增加资源(如数据库连接数)
  • 根本原因分析
    • 详细分析线程dump和日志
    • 在测试环境复现问题
    • 找到问题的根本原因
  • 预防措施
    • 修复代码问题
    • 增加监控告警
    • 优化系统架构

5.3 常见误区与陷阱

  • 认为原子操作都是线程安全的:多个原子操作组合在一起不是原子的
  • 过度使用volatile关键字:volatile不能保证原子性
  • 错误使用ThreadLocal:导致内存泄漏
  • 在finally中释放锁:避免锁泄漏
  • 不要在锁中调用外部方法:可能导致死锁
  • 不要忽略InterruptedException:正确处理线程中断
  • 不要使用stop()方法停止线程:会导致线程不安全

六、面试高频考点总结

  1. 死锁产生的四个必要条件及如何破坏这些条件
  2. 如何排查死锁:工具、步骤、分析方法
  3. 线程安全的实现方式及各自的优缺点
  4. synchronized与ReentrantLock的区别
  5. volatile关键字的作用及原理
  6. 线程的状态及转换
  7. 线程dump包含哪些信息及如何分析
  8. 常见的线程安全问题及解决方案
  9. 并发容器的实现原理:如ConcurrentHashMap
  10. 线程池的参数及工作原理

Java并发编程面试高频考点清单(按考察频率排序)

🔥 五星必考题(90%以上概率考察)

1. 死锁相关

  1. 死锁产生的四个必要条件(Coffman条件)

    • 核心答案:互斥条件、请求与保持条件、不可剥夺条件、循环等待条件
    • 延伸:如何破坏每个条件?(破坏循环等待最常用)
  2. 如何排查死锁?

    • 核心答案:
      • 工具:jstack、jconsole、jvisualvm、Arthas(deadlock命令)
      • 步骤:确认现象→获取3-5次线程dump→查找"Found one Java-level deadlock"→分析锁等待关系→定位代码
    • 延伸:生产环境如何安全获取线程dump?
  3. 死锁的解决方案

    • 核心答案:统一锁获取顺序、一次性申请所有资源、使用tryLock(timeout)、避免嵌套锁

2. 线程安全基础

  1. 什么是线程安全?线程不安全的根本原因是什么?

    • 核心答案:多线程任意调度均得正确结果;根本原因是共享可变状态+竞态条件+指令重排序+可见性问题
  2. synchronized与ReentrantLock的区别

特性 synchronized ReentrantLock
实现方式 JVM层面关键字 JDK层面API
锁类型 可重入、非公平 可重入、公平/非公平可选
中断响应 不支持 支持lockInterruptibly()
超时获取 不支持 支持tryLock(timeout)
条件变量 不支持 支持多个Condition
释放方式 自动释放 必须在finally中手动释放
  1. volatile关键字的作用与原理

    • 核心答案:保证可见性、禁止指令重排序;不保证原子性
    • 原理:内存屏障(Load屏障、Store屏障)
    • 适用场景:一写多读、状态标记、DCL单例模式
  2. Java内存模型(JMM)三大特性

    • 核心答案:原子性、可见性、有序性
    • 延伸:每个特性分别由哪些机制保证?

3. 线程与线程池

  1. Java线程的状态及转换流程

    • 核心答案:NEW→RUNNABLE→BLOCKED/WAITING/TIMED_WAITING→TERMINATED
    • 重点:各状态转换的触发条件
  2. 线程池的核心参数及工作原理

    • 核心参数:核心线程数、最大线程数、空闲时间、工作队列、拒绝策略
    • 工作原理:任务提交→核心线程处理→入队→非核心线程处理→拒绝策略
    • 延伸:如何合理设置线程池参数?
  3. 常见的线程池类型及适用场景

    • 核心答案:
      • FixedThreadPool:固定线程数,适用于任务量稳定的场景
      • CachedThreadPool:缓存线程池,适用于大量短任务
      • SingleThreadExecutor:单线程池,保证任务顺序执行
      • ScheduledThreadPool:定时任务线程池
    • 延伸:为什么不推荐使用Executors创建线程池?

4. 并发容器

  1. ConcurrentHashMap的实现原理(JDK7 vs JDK8)
    • JDK7:分段锁(Segment),数组+链表
    • JDK8:CAS+synchronized,数组+链表+红黑树,锁粒度更细
    • 延伸:为什么JDK8放弃分段锁?

⭐⭐⭐⭐ 四星高频题(70%以上概率考察)

1. 线程安全问题

  1. 常见的线程安全问题类型及解决方案

    • 竞态条件:check-then-act、read-modify-write
    • 可见性问题、有序性问题
    • 解决方案:同步机制、原子类、并发容器、线程封闭、不可变对象
  2. 什么是竞态条件?举个例子

    • 核心答案:多个线程对共享变量的执行顺序影响结果
    • 例子:count++、单例模式的双重检查锁定问题
  3. ThreadLocal的实现原理及内存泄漏问题

    • 原理:每个Thread持有一个ThreadLocalMap,key为ThreadLocal弱引用,value为线程私有值
    • 内存泄漏:ThreadLocal被回收后,key变为null,value无法被回收
    • 解决方案:使用完ThreadLocal后调用remove()方法

2. 线程Dump分析

  1. 线程Dump包含哪些信息?

    • 核心答案:线程名称、ID、优先级、状态、调用堆栈、锁信息、死锁信息
  2. 如何通过线程Dump分析常见问题?

    • 死锁:查找"Found one Java-level deadlock"
    • 锁竞争:大量BLOCKED线程等待同一个锁
    • 死循环:单个线程长时间处于RUNNABLE状态+高CPU
    • 资源等待:大量WAITING/TIMED_WAITING线程
  3. 生成线程Dump的方法有哪些?

    • 命令行:jstack、jcmd、kill -3(Linux)
    • 图形化:jvisualvm、jconsole
    • 代码:ThreadMXBean.dumpAllThreads()

3. 并发工具类

  1. 原子类的实现原理(CAS)

    • 核心答案:Compare And Swap,无锁并发
    • 缺点:ABA问题、循环时间长开销大、只能保证一个变量的原子性
    • 解决方案:AtomicStampedReference解决ABA问题
  2. CountDownLatch、CyclicBarrier、Semaphore的区别

工具 作用 适用场景 可重用性
CountDownLatch 一个线程等待多个线程完成 主线程等待子线程初始化
CyclicBarrier 多个线程互相等待 多线程计算结果合并
Semaphore 控制同时访问资源的线程数 限流
  1. 读写锁(ReentrantReadWriteLock)的原理及适用场景
    • 原理:读锁共享、写锁独占
    • 适用场景:读多写少
    • 缺点:写锁饥饿问题(StampedLock解决)

⭐⭐⭐ 三星常考题(50%左右概率考察)

1. 基础理论

  1. happens-before原则

    • 核心答案:判断数据是否存在竞争、线程是否安全的核心依据
    • 常见规则:程序次序规则、锁定规则、volatile变量规则、传递性规则
  2. 进程与线程的区别

    • 核心答案:进程是资源分配单位,线程是CPU调度单位;线程共享进程资源
  3. 上下文切换的开销

    • 核心答案:寄存器保存与恢复、内核态/用户态切换、缓存失效

2. 实战问题

  1. 如何定位线程安全问题?

    • 工具:静态分析(SpotBugs、SonarQube)、动态分析(Arthas、JFR)、并发测试(jcstress)
    • 步骤:复现问题→收集信息→分析问题→验证问题→修复问题
  2. 生产环境遇到并发问题如何应急处理?

    • 快速定位:jstack/Arthas生成线程dump
    • 临时解决:重启应用、调整线程池参数、增加资源
    • 根本解决:分析dump和日志、测试环境复现、修复代码
  3. 什么是对象逸出?如何避免?

    • 核心答案:对象在构造完成前就被发布
    • 避免:不要在构造函数中启动线程、不要在构造函数中暴露this引用

3. 高级进阶

  1. StampedLock的原理及优势

    • 核心答案:乐观读模式,比读写锁性能更高
    • 优势:解决了读写锁的写锁饥饿问题
  2. 什么是锁升级?synchronized的锁升级过程

    • 核心答案:无锁→偏向锁→轻量级锁→重量级锁
    • 触发条件:竞争程度逐渐增加
  3. AQS(抽象队列同步器)的原理

    • 核心答案:基于CLH队列,使用volatile state变量表示同步状态
    • 实现:ReentrantLock、CountDownLatch、Semaphore等都基于AQS

💡 面试加分题(考察深度与实战经验)

  1. 你在项目中遇到过哪些并发问题?如何解决的?
  2. 如何设计一个线程安全的单例模式?(至少写出3种)
  3. 如何避免死锁?在项目中你是怎么做的?
  4. 什么是伪共享?如何避免?
  5. Java 8之后并发包有哪些新特性?
  6. 如何进行并发性能调优?
  7. 分布式环境下如何解决并发问题?
  8. 什么是线程泄漏?如何排查和避免?

Java并发编程面试高频考点问答卡片(按考察频率排序)

🔥 五星必考题(90%以上概率考察)


卡片1

问题:死锁产生的四个必要条件是什么?如何破坏这些条件?
标准答案

  • 四个必要条件(Coffman条件):
    1. 互斥条件:资源同一时间只能被一个线程持有
    2. 请求与保持条件:线程已持有至少一个资源,又请求其他被持有的资源
    3. 不可剥夺条件:资源不能被强制剥夺,只能由持有者主动释放
    4. 循环等待条件:线程之间形成循环等待资源的关系
  • 破坏方法:
    • 破坏循环等待(最常用):统一锁获取顺序、按数字编号排序
    • 破坏请求与保持:一次性申请所有资源、申请失败释放已持资源
    • 破坏不可剥夺:使用tryLock(long timeout)超时自动释放
    • 破坏互斥:使用原子类代替锁、读写锁提高并发性

卡片2

问题:如何排查死锁?
标准答案

  1. 确认现象:系统无响应、CPU使用率低但负载高、业务操作超时、日志出现大量线程阻塞信息
  2. 获取线程dump:使用jstack -l <pid>jcmd <pid> Thread.print,连续获取3-5次,间隔10-20秒
  3. 分析dump:查找"Found one Java-level deadlock:"字样,查看死锁线程的堆栈和锁等待关系
  4. 定位代码:根据堆栈信息找到对应的代码行,分析锁的获取顺序
  5. 验证修复:在测试环境复现问题并验证修复方案

生产环境注意:jstack对性能影响很小,可安全使用;避免在系统负载极高时强制生成dump


卡片3

问题:synchronized与ReentrantLock的区别是什么?
标准答案

特性 synchronized ReentrantLock
实现层面 JVM内置关键字 JDK层面API实现
锁类型 可重入、非公平锁 可重入、公平/非公平锁可选
中断响应 不支持,线程会一直阻塞 支持lockInterruptibly()方法
超时获取 不支持 支持tryLock(long timeout, TimeUnit unit)
条件变量 不支持 支持多个Condition对象
释放方式 自动释放(退出代码块或异常) 必须在finally块中手动调用unlock()
性能 低竞争下性能好 高竞争下性能更稳定

卡片4

问题:volatile关键字的作用是什么?原理是什么?
标准答案

  • 核心作用
    1. 保证可见性:一个线程修改了volatile变量,其他线程能立即看到最新值
    2. 禁止指令重排序:编译器和CPU不会对volatile变量的读写操作进行重排序
    3. 不保证原子性:不能保证count++这类复合操作的原子性
  • 实现原理:通过插入内存屏障实现
    • 写volatile变量时,在写操作后插入Store屏障,将本地内存数据刷新到主内存
    • 读volatile变量时,在读操作前插入Load屏障,从主内存读取最新数据
  • 适用场景:一写多读、状态标记、双重检查锁定(DCL)单例模式

卡片5

问题:Java线程池的核心参数有哪些?工作原理是什么?
标准答案

  • 核心参数
    1. corePoolSize:核心线程数,长期存活的线程数量
    2. maximumPoolSize:最大线程数,线程池允许的最大线程数量
    3. keepAliveTime:非核心线程的空闲存活时间
    4. workQueue:工作队列,用于存放等待执行的任务
    5. handler:拒绝策略,当任务无法处理时的处理方式
  • 工作原理
    1. 任务提交时,若核心线程数未满,创建核心线程执行任务
    2. 若核心线程数已满,将任务加入工作队列
    3. 若工作队列已满,创建非核心线程执行任务
    4. 若总线程数达到maximumPoolSize,执行拒绝策略

卡片6

问题:ConcurrentHashMap在JDK7和JDK8中的实现原理有什么区别?
标准答案

  • JDK7实现
    • 采用分段锁(Segment)机制,将数据分成多个Segment,每个Segment独立加锁
    • 数据结构:Segment数组 + HashEntry数组 + 链表
    • 并发度:默认16,最多支持16个线程同时写
  • JDK8实现
    • 放弃分段锁,采用CAS + synchronized机制
    • 数据结构:Node数组 + 链表 + 红黑树(链表长度超过8转为红黑树)
    • 锁粒度:只锁当前链表的头节点,并发度更高
    • 优势:性能提升明显,解决了JDK7中分段锁粒度较粗的问题

⭐⭐⭐⭐ 四星高频题(70%以上概率考察)


卡片7

问题:什么是线程安全?线程不安全的根本原因是什么?
标准答案

  • 线程安全定义:多线程环境下,无论系统如何调度线程,程序都能得到正确的结果
  • 根本原因
    1. 共享可变状态:多个线程同时访问和修改同一个变量
    2. 竞态条件:多个线程对共享变量的执行顺序影响最终结果
    3. 指令重排序:编译器和CPU为了优化性能,会对指令进行重排序
    4. 内存可见性问题:一个线程修改了共享变量,其他线程可能看不到修改结果

卡片8

问题:ThreadLocal的实现原理是什么?为什么会发生内存泄漏?
标准答案

  • 实现原理
    • 每个Thread对象都持有一个ThreadLocalMap对象
    • ThreadLocalMap的key是ThreadLocal对象的弱引用,value是线程私有值
    • 每个线程只能访问自己的ThreadLocalMap,实现了线程隔离
  • 内存泄漏原因
    • ThreadLocal被回收后,ThreadLocalMap中的key变为null
    • 但value是强引用,不会被回收,导致value一直占用内存
  • 解决方案:使用完ThreadLocal后,必须调用remove()方法手动清理

卡片9

问题:CountDownLatch、CyclicBarrier、Semaphore的区别是什么?
标准答案

工具类 核心作用 适用场景 可重用性
CountDownLatch 一个线程等待多个线程完成 主线程等待子线程初始化完成 否(计数到0后无法重置)
CyclicBarrier 多个线程互相等待,达到屏障点后一起执行 多线程计算结果合并 是(可重置计数)
Semaphore 控制同时访问特定资源的线程数量 限流、资源池控制 是(可释放和重新获取许可)

卡片10

问题:Java线程有哪些状态?状态之间如何转换?
标准答案

  • 6种线程状态
    1. NEW:线程已创建但未调用start()方法
    2. RUNNABLE:线程正在JVM中执行,或等待操作系统资源(如CPU)
    3. BLOCKED:线程阻塞,等待获取监视器锁
    4. WAITING:线程无限期等待另一个线程执行特定操作(如wait()join()
    5. TIMED_WAITING:线程在指定时间内等待(如sleep()wait(long)
    6. TERMINATED:线程已执行完毕
  • 主要转换流程
    • NEW → RUNNABLE:调用start()方法
    • RUNNABLE → BLOCKED:等待获取synchronized锁
    • RUNNABLE → WAITING:调用wait()join()LockSupport.park()
    • RUNNABLE → TIMED_WAITING:调用sleep()wait(long)join(long)
    • BLOCKED/WAITING/TIMED_WAITING → RUNNABLE:获取到锁、被唤醒、等待超时
    • RUNNABLE → TERMINATED:线程执行完毕或抛出未捕获异常

卡片11

问题:如何通过线程Dump分析常见问题?
标准答案

  1. 死锁问题
    • 特征:dump开头有"Found one Java-level deadlock:"字样
    • 分析:查看死锁线程的锁等待关系,找到互相等待的锁和线程
  2. 锁竞争问题
    • 特征:大量线程处于BLOCKED状态,都在等待同一个锁
    • 分析:找到持有锁的线程,查看其堆栈,分析锁持有时间过长的原因
  3. 死循环问题
    • 特征:单个线程长时间处于RUNNABLE状态,CPU使用率很高
    • 分析:查看该线程的调用堆栈,找到死循环的代码位置
  4. 资源等待问题
    • 特征:大量线程处于WAITING或TIMED_WAITING状态
    • 分析:查看线程等待的资源类型(数据库连接、线程池、网络IO等)

卡片12

问题:原子类的实现原理是什么?CAS有什么缺点?
标准答案

  • 实现原理:基于CAS(Compare And Swap)操作实现无锁并发
    • CAS包含三个操作数:内存位置V、预期值A、新值B
    • 当且仅当V的值等于A时,将V的值更新为B,否则什么都不做
    • 整个操作是原子的,由CPU指令保证
  • CAS的缺点
    1. ABA问题:变量值从A变成B,又变回A,CAS会误认为没有变化
    2. 循环时间长开销大:竞争激烈时,CAS会一直自旋,消耗CPU资源
    3. 只能保证一个变量的原子性:无法同时保证多个变量的原子操作
  • ABA问题解决方案:使用AtomicStampedReference,通过版本号标记变量变化

⭐⭐⭐ 三星常考题(50%左右概率考察)


卡片13

问题:什么是happens-before原则?有哪些常见规则?
标准答案

  • 定义:JMM定义的一套规则,用于判断两个操作之间是否存在顺序关系,是判断数据是否存在竞争、线程是否安全的核心依据
  • 常见规则
    1. 程序次序规则:同一个线程内,前面的操作happens-before后面的操作
    2. 锁定规则:对一个锁的解锁操作happens-before后续对同一个锁的加锁操作
    3. volatile变量规则:对一个volatile变量的写操作happens-before后续对该变量的读操作
    4. 传递性规则:如果A happens-before B,B happens-before C,那么A happens-before C
    5. 线程启动规则:Thread.start()方法happens-before该线程的所有操作
    6. 线程终止规则:线程的所有操作happens-before其他线程检测到该线程终止

卡片14

问题:什么是锁升级?synchronized的锁升级过程是怎样的?
标准答案

  • 锁升级定义:JDK6为了优化synchronized的性能,引入了锁升级机制,锁会随着竞争程度的增加而逐渐升级
  • 升级过程
    1. 无锁状态:对象刚创建时,没有任何线程竞争
    2. 偏向锁:第一个线程访问对象时,对象头会记录该线程ID,以后该线程访问时无需加锁
    3. 轻量级锁:当有第二个线程竞争偏向锁时,偏向锁升级为轻量级锁,使用CAS操作获取锁
    4. 重量级锁:当轻量级锁自旋超过一定次数(默认10次)仍未获取到锁,升级为重量级锁,线程会被阻塞

注意:锁只能升级,不能降级


卡片15

问题:AQS(抽象队列同步器)的原理是什么?
标准答案

  • 定义:AQS是Java并发包中用于实现锁和同步器的基础框架
  • 核心原理
    1. 使用一个volatile int state变量表示同步状态
    2. 维护一个CLH双向队列,用于存放等待获取锁的线程
    3. 线程获取锁失败时,会被加入队列并阻塞
    4. 当锁被释放时,会唤醒队列中的头节点线程
  • 实现方式
    • 独占模式:同一时间只能有一个线程获取锁(如ReentrantLock)
    • 共享模式:同一时间可以有多个线程获取锁(如CountDownLatch、Semaphore)
  • 常见实现类:ReentrantLock、ReentrantReadWriteLock、CountDownLatch、CyclicBarrier、Semaphore

卡片16

问题:为什么不推荐使用Executors创建线程池?
标准答案

  • Executors提供的默认线程池存在以下问题:
    1. FixedThreadPool和SingleThreadExecutor:使用无界的LinkedBlockingQueue,任务队列可以无限增长,可能导致OOM
    2. CachedThreadPool:最大线程数为Integer.MAX_VALUE,可能创建大量线程,导致OOM
    3. ScheduledThreadPool:最大线程数为Integer.MAX_VALUE,同样可能创建大量线程
  • 推荐做法:使用ThreadPoolExecutor的构造方法手动创建线程池,根据业务场景合理设置核心参数,特别是工作队列的容量和拒绝策略

💡 面试加分题(考察深度与实战经验)


卡片17

问题:如何设计一个线程安全的单例模式?
标准答案

  1. 饿汉式:类加载时初始化,线程安全,但可能造成资源浪费
    public class Singleton {
         
        private static final Singleton INSTANCE = new Singleton();
        private Singleton() {
         }
        public static Singleton getInstance() {
          return INSTANCE; }
    }
    
  2. 双重检查锁定(DCL):懒加载,线程安全,性能高
    public class Singleton {
         
        private static volatile Singleton instance;
        private Singleton() {
         }
        public static Singleton getInstance() {
         
            if (instance == null) {
         
                synchronized (Singleton.class) {
         
                    if (instance == null) {
         
                        instance = new Singleton();
                    }
                }
            }
            return instance;
        }
    }
    
  3. 静态内部类:懒加载,线程安全,实现简单(推荐)
    public class Singleton {
         
        private Singleton() {
         }
        private static class SingletonHolder {
         
            private static final Singleton INSTANCE = new Singleton();
        }
        public static Singleton getInstance() {
         
            return SingletonHolder.INSTANCE;
        }
    }
    
  4. 枚举:最简单,绝对线程安全,防止反射和序列化破坏
    public enum Singleton {
         
        INSTANCE;
        public void doSomething() {
         }
    }
    

卡片18

问题:什么是伪共享?如何避免伪共享?
标准答案

  • 伪共享定义:CPU缓存以缓存行(通常64字节)为单位存储数据,当多个线程同时修改同一个缓存行中的不同变量时,会导致缓存行频繁失效,严重影响性能
  • 避免方法
    1. 缓存行填充:在变量前后添加7个long类型的变量,使每个变量独占一个缓存行
    2. 使用@Contended注解:JDK8及以上版本提供,自动为变量添加填充
    3. 数据结构优化:将经常被不同线程访问的变量分开存储

卡片19

问题:你在项目中遇到过哪些并发问题?如何解决的?
标准答案:(结合实际项目经验回答,以下为参考框架)

  • 问题1:死锁问题
    • 现象:系统某个功能突然无响应,CPU使用率低但负载高
    • 排查:使用jstack获取线程dump,发现两个线程互相等待对方释放锁
    • 原因:两个方法获取锁的顺序不一致
    • 解决:统一锁的获取顺序,按数字编号排序
  • 问题2:线程安全问题
    • 现象:统计数据不准确,偶尔出现负数
    • 排查:发现使用了普通的int变量进行计数,多线程同时修改
    • 解决:使用AtomicInteger代替int变量
  • 问题3:线程池耗尽问题
    • 现象:系统响应越来越慢,最终无响应
    • 排查:使用jstack发现大量线程处于WAITING状态,线程池队列已满
    • 原因:线程池参数设置不合理,核心线程数太小,任务执行时间过长
    • 解决:调整线程池参数,优化任务执行逻辑

卡片20

问题:生产环境遇到并发问题如何应急处理?
标准答案

  1. 快速止损
    • 若问题严重影响业务,立即重启应用
    • 若只是部分功能受影响,可先将流量切到备用节点
  2. 保留现场
    • 重启前务必获取线程dump、内存dump、GC日志和应用日志
    • 记录系统当时的CPU、内存、IO等资源使用情况
  3. 问题定位
    • 分析线程dump,查看是否有死锁、锁竞争、死循环等问题
    • 结合应用日志,找到问题发生的时间点和相关代码
  4. 临时解决
    • 调整线程池参数、增加数据库连接数等
    • 临时关闭非核心功能,减轻系统压力
  5. 根本解决
    • 在测试环境复现问题,找到根本原因
    • 修复代码,发布补丁版本
  6. 预防措施
    • 增加监控告警,及时发现并发问题
    • 编写并发测试用例,提前发现潜在问题

📚 背诵建议

  1. 优先背诵五星必考题,这是面试中最常问的内容
  2. 对于对比类问题(如synchronized vs ReentrantLock),重点记住表格中的核心区别
  3. 对于原理类问题,先理解再背诵,不要死记硬背
  4. 加分题一定要结合自己的实际项目经验回答,这样才能给面试官留下深刻印象
相关文章
|
8天前
|
人工智能 开发工具 iOS开发
Claude Code 新手完全上手指南:安装、国产模型配置与常用命令全解
Claude Code 是一款运行在终端环境中的 AI 编程助手,能够直接在命令行中完成代码生成、项目分析、文件修改、命令执行、Git 管理等开发全流程工作。它最大的特点是**任务驱动、终端原生、轻量高效、多模型兼容**,无需图形界面、不依赖 IDE 插件,能够深度融入开发者日常工作流。
3013 7
|
11天前
|
Shell API 开发工具
Claude Code 快速上手指南(新手友好版)
AI编程工具卷疯啦!Claude Code凭借任务驱动+终端原生的特性,成了开发者的效率搭子。本文从安装、登录、切换国产模型到常用命令,手把手带新手快速上手,全程避坑,30分钟独立用起来。
3106 20
|
23天前
|
人工智能 JSON 供应链
畅用7个月无影 JVS Claw |手把手教你把JVS改造成「科研与产业地理情报可视化大师」
LucianaiB分享零成本畅用JVS Claw教程(学生认证享7个月使用权),并开源GeoMind项目——将JVS改造为科研与产业地理情报可视化AI助手,支持飞书文档解析、地理编码与腾讯地图可视化,助力产业关系图谱构建。
23568 15
畅用7个月无影 JVS Claw |手把手教你把JVS改造成「科研与产业地理情报可视化大师」
|
4天前
|
人工智能 Linux BI
国内用 Claude Code 终于不用翻墙了:一行命令搞定,自动接 DeepSeek
JeecgBoot AI专题研究 一键脚本:Claude Code + JeecgBoot Skills + DeepSeek 全平台接入 一行命令装好 Claude Code + JeecgBoot Skills + DeepSeek 接入,无需翻墙使用 Claude Code,支持 Wind
1993 3
国内用 Claude Code 终于不用翻墙了:一行命令搞定,自动接 DeepSeek
|
10天前
|
人工智能 JSON BI
DeepSeek V4-Pro 接入 Claude Code 完全实战:体验、测试与关键避坑指南
Claude Code 作为当前主流的 AI 编程辅助工具,凭借强大的代码理解、工程执行与自动化能力深受开发者喜爱,但原生模型的使用成本相对较高。为了在保持能力的同时进一步降低开销,不少开发者开始寻找兼容度高、价格更友好的替代模型。DeepSeek V4 系列的发布带来了新的选择,该系列包含 V4-Pro 与 V4-Flash 两款模型,并提供了与 Anthropic 完全兼容的 API 接口,理论上只需简单修改配置,即可让 Claude Code 无缝切换为 DeepSeek 引擎。
2509 3
|
9天前
|
人工智能 安全 开发工具
Claude Code 官方工作原理与使用指南
Claude Code 不是传统代码补全工具,而是 Anthropic 推出的终端 AI 代理,具备代理循环、双驱动架构(模型+工具)、全局项目感知、6 种权限模式等核心能力,本文基于官方文档系统解析其工作原理与高效使用技巧。
1371 0
|
9天前
|
存储 Linux iOS开发
【2026最新】MarkText中文版Markdown编辑器使用图解(附安装包)
MarkText是一款免费开源、跨平台的Markdown编辑器,主打所见即所得实时预览,支持Windows/macOS/Linux。内置数学公式、流程图、代码高亮、多主题及PDF/HTML导出,是Typora的轻量免费替代首选。(239字)