死锁产生条件与诊断:jps、jstack、VisualVM

发布时间:2026/6/17 11:47:21
死锁产生条件与诊断:jps、jstack、VisualVM 死锁题很容易被答成一句话两个线程互相等待。这句话当然对但面试里不够。更完整的回答应该包括三层死锁怎么写出来的。死锁成立需要哪些条件。线上或本地怎么定位。一个最典型的死锁两个线程分别持有一把锁又去等待对方手里的锁。ObjectAnewObject();ObjectBnewObject();Threadt1newThread(()-{synchronized(A){System.out.println(lock A);sleep(1000);synchronized(B){System.out.println(lock B);}}},t1);Threadt2newThread(()-{synchronized(B){System.out.println(lock B);sleep(500);synchronized(A){System.out.println(lock A);}}},t2);t1.start();t2.start();执行到某个时刻线程t1持有锁 A等待锁 B。线程t2持有锁 B等待锁 A。谁都不肯放谁都往下走不了。线程 t2锁 B锁 A线程 t1线程 t2锁 B锁 A线程 t1互相等待程序无法继续获取锁 A获取锁 B等待锁 B等待锁 A死锁的四个必要条件经典死锁有四个必要条件条件含义互斥条件资源同一时刻只能被一个线程占有占有且等待线程持有资源的同时还在等待其他资源不可抢占资源不能被外部强行剥夺只能由持有者释放循环等待多个线程形成首尾相接的等待环死锁成立互斥占有且等待不可抢占循环等待锁一次只能一个线程持有拿着 A 等 B别人不能强行夺走 At1 等 t2, t2 等 t1预防死锁本质就是破坏其中一个条件。工程上最常见的是破坏循环等待固定加锁顺序。比如所有线程都先拿 A再拿 B就不会出现一个拿 A 等 B、另一个拿 B 等 A。线程 t1先获取锁 A线程 t2再获取锁 B执行临界区怎么用 jps 和 jstack 诊断当 Java 程序疑似卡住可以先用jps找进程。jps-l找到目标进程 ID 后用jstack打线程栈jstack-lpid如果确实发生死锁jstack通常会在输出里给出类似信息Found one Java-level deadlock:并列出哪些线程持有哪些锁、正在等待哪些锁。诊断链路可以这样看是否程序卡死 / 请求无响应jps -l 找 Java 进程jstack -l pid 导出线程栈是否发现 deadlock查看线程名、锁对象、代码行回到代码分析加锁顺序继续排查 BLOCKED / WAITING / IO 等问题jstack的价值不只是告诉你“有死锁”。更关键的是告诉你线程卡在哪一行、等哪把锁、这把锁被谁持有。可视化工具本地开发或测试环境也可以用 GUI 工具看线程。工具作用jconsole监控 JVM 内存、线程、类加载等信息VisualVM查看线程、CPU、内存、堆栈、对象分配等它们适合辅助定位但线上排障不要只依赖 GUI。最通用、最容易落地的还是jpsjstack因为服务器上通常能直接执行。如何修复死锁修复死锁不要只盯着“加大超时时间”。超时只能缓解不能解释为什么锁顺序会错。常见修复方式固定加锁顺序所有线程都按同一顺序获取多把锁。缩小锁粒度减少一次持有多把锁的场景。用tryLock加超时获取不到锁时主动释放已持有资源并重试。避免在持锁期间调用外部服务、数据库、RPC 这类不可控耗时操作。用tryLock的思想大概是if(lockA.tryLock(1,TimeUnit.SECONDS)){try{if(lockB.tryLock(1,TimeUnit.SECONDS)){try{// do something}finally{lockB.unlock();}}}finally{lockA.unlock();}}这不是鼓励所有地方都写复杂的tryLock而是说明当业务确实需要多把锁时至少要设计失败退出路径。面试怎么答可以这样答死锁通常发生在多个线程需要同时获取多把锁时。比如线程 1 持有 A 锁等待 B 锁线程 2 持有 B 锁等待 A 锁就会互相等待程序无法继续。死锁有四个必要条件互斥、占有且等待、不可抢占、循环等待。解决死锁就是破坏其中一个条件项目里最常见的是固定加锁顺序避免循环等待。诊断时可以先用jps -l找 Java 进程再用jstack -l pid查看线程栈。如果有 Java 级死锁jstack会打印 deadlock 信息并指出线程持有和等待的锁。也可以用jconsole、VisualVM 这类工具辅助查看线程状态。