Java线程卡死问题定位
最近在做ftp下载的时候遇到一个诡异的问题,一个方法调了几次后一点反应也没有,连日志都不打,代码如下,可以思考下为啥方法调用后日志都不打
其实答案很简单,这个方法加了锁,肯定是有其他线程占用了这个锁,导致后续的方法无法执行,但是最诡异的不是这个,而是等了一个晚上这个锁都没被释放,到底是什么地方导致了这个线程的卡死呢?
这时我们就要通过查看线程堆栈信息来看下线程运行情况:
1. 确定卡死线程的 PID
首先需要获取 Java 进程的 ID(PID):
# 查找Java进程(包含main类或关键词)
ps aux | grep java
# 或使用jps(JDK自带工具)
jps -l
2. 生成线程 dump 文件
使用 jstack
命令生成线程堆栈信息,下面的12345就是pid:
jstack -l 12345 > thread_dump.txt
-l
参数:显示锁的详细信息(如等待的锁、持有锁的线程)。- 输出保存到
thread_dump.txt
文件以便分析。
3. 分析线程状态
在线程 dump 文件中,查找以下关键信息:
3.1 寻找卡死线程的状态
常见的卡死状态包括:
BLOCKED
:线程正在等待获取锁。WAITING
或TIMED_WAITING
:线程在等待某个条件(如Object.wait()
、Thread.sleep()
)。RUNNABLE
:线程看似在运行,但可能在执行死循环或 IO 阻塞。
3.2 查找锁争用信息
重点关注以下内容:
- 锁的持有者:找到持有锁的线程(通常显示为
locked <0x00000000>
)。 - 等待锁的线程:显示为
waiting to lock <0x00000000>
。
具体分析
我们先查找lock的线程,发现好几个线程是blocked状态,上面还有线程名称,所以以后遇到一个线程比较耗时的情况,建议使用乐观锁,遇到失败快速返回,不然大量线程处于等待中容易耗光线程。
在这个信息里面我们还能看到代码执行到哪个类的第几行遇到了锁,并且指明了等待哪个锁,waiting to lock xxxx,我们只要根据这个锁的编号就能找到卡死的线程了
接下去我们找到一个运行中的线程,通过locked xxxx我们确认了这个线程持有锁 ,上面的FilePartServiceImpl.java:986表示这个线程当前运行的位置
我们通过这些信息找到堵塞的代码
原来线程是卡在ftp下载这一步了
优化建议
1.使用分布式锁的乐观锁模式代替synchronized
RLock lock = redissonClient.getLock(ValueConstant.REDIS_SYNC_FTP_FILEPART);
if (!lock.tryLock()) {// 没有拿到锁直接返回throw new ServiceFailException("同步线程运行中,请稍后再试");
}
try {//业务处理
} finally{//释放锁if (lock.isLocked() && lock.isHeldByCurrentThread()) {lock.unlock();}
}
2.使用ftp保活机制
setControlKeepAliveTimeout(long controlIdleSeconds)
用于设置控制连接保活消息的发送间隔时间,防止在长时间文件传输(上传 / 下载)期间连接因空闲被服务器关闭。
//设置保活时间,保持连接
ftp.getClient().setControlKeepAliveTimeout(30);
3.关于涉及网络操作的建议
凡是涉及到网络方面的操作,像ftp,http等的调用,一定要考虑保活或者设置超时时间,否则很容易造成线程卡死