Blocked线程未显式出现synchronized块,是因为它在等待第三方库(如Jackson、HikariCP、OkHttp)内部持有的monitor锁;需通过waiting to lock与locked地址匹配,并结合jmap、jcmd或-XX:+PrintConcurrentLocks定位具体锁对象。

Blocked 线程的调用链里没有 synchronized 块?先确认是不是 MonitorEntry 竞争
看到 jstack 输出中线程状态为 java.lang.Thread.State: BLOCKED (on object monitor),但堆栈末尾没出现你代码里的 synchronized 方法或块,别急着怀疑业务逻辑——大概率是它在等某个第三方库内部持有的锁。关键看 waiting to lock 后面的地址,和另一个线程的 locked 是否匹配。
实操建议:
- 用
grep -A 10 "BLOCKED" jstack.log提取所有 BLOCKED 线程,再逐个查waiting to lock的对象地址 - 对每个地址,反向搜索整个日志:例如
grep -B 5 -A 5 "0x0000000712345678" jstack.log,找谁正持有该锁 - 特别注意持有锁的线程是否卡在第三方库调用里(如
com.fasterxml.jackson.databind.、org.apache.http.impl.conn.、com.zaxxer.hikari.)
第三方库常见隐形锁源:Jackson、OkHttp、HikariCP 的典型陷阱
这些库不显式暴露 synchronized,但内部用 static final Object 或单例实例做同步点,容易被多线程高频调用时堵死。
典型场景与参数差异:
-
ObjectMapper实例本应复用,但若误写成每次 new(尤其在 Spring @Configuration 中未声明为 @Bean),会导致大量线程竞争其内部_serializerProvider的锁 -
OkHttpClient的ConnectionPool使用ReentrantLock,但日志里只显示parking to wait for—— 这类 AQS 锁在jstack中仍归为 BLOCKED,需结合源码确认锁对象 -
HikariCP的ConcurrentBag在获取连接时会竞争一个sharedList上的锁;若连接池配置过小(maximumPoolSize=2)+ 长事务 + 多线程并发,极易触发
光靠 jstack 不够:必须补全锁对象身份和调用上下文
jstack 只给对象哈希地址,不告诉你这是哪个类的哪个静态字段。不补这层,90% 的“第三方死锁”分析会停在“知道有锁,但不知道谁在管它”。
实操建议:
- 用
jmap -histo:live <pid></pid>查该地址对应类的实例数量,缩小范围(比如com.fasterxml.jackson.databind.ser.SerializerProvider实例数异常高) - 用
jcmd <pid> VM.native_memory summary</pid>看是否伴随Internal内存持续增长——这是java.util.concurrent.locks类锁竞争的间接信号 - 加 JVM 参数
-XX:+PrintConcurrentLocks(仅限 HotSpot 8u60+),让jstack输出额外包含锁的 Java 类名(如- locked (a com.zaxxer.hikari.util.ConcurrentBag$BagEntry))
验证是否真死锁:别信 “两个线程互相等”,要看锁获取顺序是否循环
很多 case 表面像死锁,其实是单点锁瓶颈(比如 20 个线程都等同一个 Jackson 序列化锁)。真正死锁必须满足:线程 A 持有锁 X 等待锁 Y,线程 B 持有锁 Y 等待锁 X。
检查要点:
- 把所有
waiting to lock和locked地址整理成表格,画依赖图;只要出现环,就是死锁 - 重点关注跨库调用链:比如线程 A 在
hikari.getConnection()持有ConcurrentBag锁,同时调用了objectMapper.writeValueAsString();而线程 B 反过来先序列化、再取连接——这种混合调用最容易埋雷 - 用
jstack <pid> | grep -E "(BLOCKED|DEADLOCK)"</pid>快速筛查,但注意 JDK 8 默认不自动检测非Object.wait()类型的死锁,得靠人肉
第三方库的锁往往藏在初始化路径或缓存填充逻辑里,第一次调用慢、后续快,所以问题常在服务刚启动或流量突增时爆发。别只盯着高峰时段日志,冷启动阶段的 jstack 更可能抓到锁争夺源头。


















