jdbc取null字段本身不会卡死线程,真正卡死源于catch块中阻塞操作;需用jstack定位waiting/blocked线程,结合wasnull()判空和无i/o的异常处理预防。
数据库字段为 null 时,jdbc 在 resultset 中取值若未做空值判断,直接调用 getxxx() 方法(如 getstring()、getint()),本身不会卡死线程——但若后续逻辑在 catch 块中处理不当,就可能引发线程池卡死。真正卡死的根源,往往不是空值本身,而是异常捕获后陷入无限等待、死循环、同步锁争抢或资源未释放等行为。
重点看 catch 块里有没有“阻塞型操作”
很多同学以为 try-catch 是安全兜底,其实恰恰是 catch 里埋雷最多。常见高危写法包括:
- 在 catch 中调用另一个需要数据库连接的方法,而该连接来自同一连接池(比如 Druid)且连接已耗尽 → 新请求被阻塞在 getConnection(),线程挂起
- catch 中执行了
synchronized同步块,且锁对象是静态/全局实例,而另一个线程正持锁执行慢操作(如 FTP 下载、大文件解析)→ 当前线程 WAITING,无法释放线程资源 - catch 中调用了
Thread.sleep(5000)或Object.wait()且未设超时或未被 notify → 线程长期 TIMED_WAITING,占用线程池名额 - catch 中重试逻辑没加次数限制和退避策略,反复尝试失败操作(如不断查一个永远返回 NULL 的字段)→ 快速堆积任务,压垮线程池队列
检查 JDBC 获取空值时的真实行为
JDBC 规范明确:对 NULL 字段调用 getXXX(),除 getXXX() 有原始类型返回值(如 getInt())会返回默认值(0)外,其余均返回 null(如 getString())。它不会抛异常,更不会卡住。真正触发异常的典型场景是:
-
getInt()配合数据库字段为 VARCHAR 且内容非数字 → 抛SQLException - 使用
getObject(Class)强转失败 → 抛SQLException - ResultSet 已关闭,还去 get → 抛
SQLException
所以,如果空值引发了异常,大概率是类型不匹配或资源状态异常,而非 NULL 本身。要确认这一点,需查日志中实际抛出的异常类名和堆栈起点。
用 jstack 定位卡在线程池哪一环
当线程池响应变慢或打满时,立刻执行:
jstack -l打开 dump.txt,聚焦三类线索:
- 搜索
waiting to lock—— 找出谁在等锁、谁持有锁;若多个线程等同一个锁(如0x000000076c275960),且持有者卡在 IO(如 socketRead、FTP、HTTP 调用),就是典型卡点 - 搜索
java.util.concurrent.ThreadPoolExecutor相关线程名(如pool-1-thread-、http-nio-),看它们的 state 是WAITING还是BLOCKED,再顺藤摸到堆栈最深层的方法(如FilePartServiceImpl.java:986) - 检查是否有大量线程停在
DruidDataSource.getConnection或AbstractQueuedSynchronizer.parkAndCheckInterrupt—— 表明连接池已满或初始化锁被占,根源常在DruidDataSource.init()死锁(如驱动类加载阶段并发触发)
预防建议:空值处理 + 异常兜底双保险
不要依赖 catch 挽救设计缺陷。推荐做法:
- 所有
getXXX()前先用wasNull()判断:String s = rs.getString("col"); if (rs.wasNull()) s = ""; - catch 块内禁止任何外部 I/O、同步块、sleep、重试;只做日志记录 + 返回默认值或封装业务异常(如
throw new BizException("字段缺失")) - 若必须重试,用带限流的异步方式(如 ScheduledExecutorService + 计数器),绝不阻塞当前业务线程
- 连接池配置启用
testOnBorrow=true和合理maxWait(如 3s),让获取不到连接时快速失败,而不是无限排队










