模式切换
性能调优与问题排查
在高并发场景下,性能调优和问题排查是确保系统稳定性和高效运行的关键。
线程池调优
如何设置合理的线程池参数。
- 核心线程数(corePoolSize):
- 根据任务类型和系统资源设置。
- CPU 密集型任务:核心线程数 ≈ CPU 核心数。
- I/O 密集型任务:核心线程数 ≈ CPU 核心数 × (1 + 平均等待时间 / 平均计算时间)。
- 最大线程数(maximumPoolSize):
- 根据系统负载和任务特性设置。
- 通常设置为核心线程数的 2~4 倍。
- 队列类型(workQueue):
- 有界队列(如
ArrayBlockingQueue
):适合任务量可控的场景。 - 无界队列(如
LinkedBlockingQueue
):适合任务量不可控的场景,但可能导致内存溢出。
- 有界队列(如
- 拒绝策略(RejectedExecutionHandler):
- AbortPolicy:直接抛出异常。
- CallerRunsPolicy:由提交任务的线程执行任务。
- DiscardPolicy:直接丢弃任务。
- DiscardOldestPolicy:丢弃队列中最旧的任务。
避免线程池的性能瓶颈。
- 任务队列过满:使用有界队列并设置合理的队列大小。
- 线程数过多:根据系统资源设置合理的最大线程数。
- 任务执行时间过长:优化任务逻辑,减少任务执行时间。
死锁与活锁
死锁的产生条件与避免方法。
死锁的产生条件(四个必要条件):
- 互斥条件:资源一次只能被一个线程占用。
- 占有并等待:线程持有资源并等待其他资源。
- 非抢占条件:线程持有的资源不能被其他线程强行抢占。
- 循环等待条件:多个线程形成资源等待的环形链。
避免死锁的方法:
- 破坏占有并等待:一次性申请所有资源。
- 破坏非抢占条件:允许抢占资源。
- 破坏循环等待条件:按顺序申请资源。
活锁的产生与解决。
活锁的产生:
- 线程不断尝试解决冲突,但始终无法取得进展。
- 示例:两个线程互相让出资源,导致任务无法完成。
解决活锁的方法:
- 引入随机等待时间,避免线程同时重试。
- 调整任务调度策略。
线程泄漏
线程泄漏的原因:
- 未正确关闭线程池:线程池未调用
shutdown()
或shutdownNow()
。 - 任务阻塞:任务长时间阻塞,导致线程无法回收。
- 线程局部变量未清理:
ThreadLocal
变量未及时清理,导致内存泄漏。
线程泄漏的排查方法
- 使用监控工具:
- 通过 JVisualVM 或 JConsole 查看线程状态。
- 检查是否有大量线程处于阻塞或等待状态。
- 代码审查:
- 检查线程池是否正确关闭。
- 检查任务逻辑是否存在阻塞或死循环。
- 日志分析:
- 记录线程的生命周期,分析线程的创建和销毁情况。
性能监控工具
JVisualVM:
- 提供线程、内存、CPU 等监控功能。
- 可以查看线程的状态(运行、等待、阻塞等)。
- 支持线程转储(Thread Dump)分析。
JConsole:
- 提供线程、内存、类加载等监控功能。
- 可以查看线程的堆栈信息。
Arthas:
阿里巴巴开源的 Java 诊断工具。
支持动态监控和诊断 Java 应用程序。
常用命令:
- thread:查看线程状态和堆栈信息。
- watch:监控方法的输入参数和返回值。
- trace:跟踪方法的调用链路。
- jad:反编译类的字节码。
bash# 查看线程状态 thread # 监控方法的输入参数和返回值 watch com.example.MyClass myMethod '{params, returnObj}' # 跟踪方法的调用链路 trace com.example.MyClass myMethod