☰
【JUC】死锁
2026/10/14 4:33:29 网站建设 项目流程

Java 多线程中的死锁(Deadlock),是两个或多个线程在执行过程中,因争夺资源而造成的一种互相等待的僵局。如果没有外力干涉,这些线程都无法继续执行。

下面从产生条件、代码示例、排查方法、预防策略几个方面来讲。


一、死锁产生的四个必要条件

死锁必须同时满足以下四个条件,缺一不可:

  1. 互斥条件:资源同一时刻只能被一个线程占用。
  2. 请求与保持条件:线程已经持有了至少一个资源,又去请求被其他线程占用的资源,且不释放自己已持有的资源。
  3. 不可剥夺条件:线程已获得的资源,在使用完之前不能被其他线程强行剥夺,只能自己释放。
  4. 循环等待条件:多个线程之间形成一种头尾相接的循环等待资源关系。

破坏其中任意一个条件,就能避免死锁。


二、经典代码示例

publicclassDeadLockDemo{privatestaticfinalObjectlockA=newObject();privatestaticfinalObjectlockB=newObject();publicstaticvoidmain(String[]args){Threadt1=newThread(()->{synchronized(lockA){System.out.println("线程1 获取 lockA");try{Thread.sleep(100);}catch(InterruptedExceptione){}synchronized(lockB){System.out.println("线程1 获取 lockB");}}});Threadt2=newThread(()->{synchronized(lockB){System.out.println("线程2 获取 lockB");try{Thread.sleep(100);}catch(InterruptedExceptione){}synchronized(lockA){System.out.println("线程2 获取 lockA");}}});t1.start();t2.start();}}
  • 线程1 持有 lockA,等待 lockB;
  • 线程2 持有 lockB,等待 lockA;
  • 两者互相等待,形成循环等待,程序卡死。

三、如何排查死锁

1. 使用jstack

jstack<pid>

输出中会看到Found one Java-level deadlock:,并列出涉及死锁的线程和持有的锁。

2. 使用jconsole/VisualVM

图形化工具,连接进程后可以检测到死锁线程。

3. 使用ThreadMXBean编程检测

ThreadMXBeanmxBean=ManagementFactory.getThreadMXBean();long[]deadlockedThreads=mxBean.findDeadlockedThreads();if(deadlockedThreads!=null){ThreadInfo[]infos=mxBean.getThreadInfo(deadlockedThreads);for(ThreadInfoinfo:infos){System.out.println(info);}}

四、预防与避免策略

1. 破坏循环等待条件(最常用)

统一加锁顺序:所有线程都按相同的顺序获取锁。

// 所有线程都先 lockA 再 lockBsynchronized(lockA){synchronized(lockB){...}}

2. 破坏请求与保持条件

一次性申请所有资源,或者申请新资源前先释放已持有的资源。

3. 破坏不可剥夺条件

使用ReentrantLock.tryLock(long timeout, TimeUnit),超时拿不到锁就放弃并释放已持有的锁。

if(lockA.tryLock(1,TimeUnit.SECONDS)){try{if(lockB.tryLock(1,TimeUnit.SECONDS)){try{/* 业务逻辑 */}finally{lockB.unlock();}}}finally{lockA.unlock();}}

4. 破坏互斥条件

一般很难破坏,因为很多资源本身就是互斥的。可以考虑用无锁结构(CAS、原子类)或读写锁来减少互斥范围。

5. 其他工程实践

  • 减小锁粒度,缩短持锁时间;
  • 避免锁嵌套;
  • 使用java.util.concurrent包中的并发工具类(如ConcurrentHashMap、Semaphore)替代手动加锁;
  • 使用带超时的锁,避免无限等待。

五、死锁 vs 活锁 vs 饥饿

概念说明
死锁线程互相等待,永远阻塞
活锁线程不断重试、不断改变状态,但无法向前推进
饥饿某线程长期得不到资源(如优先级太低)

六、小结

  • 死锁的本质是循环等待 + 不可剥夺;
  • 排查靠jstack、jconsole、ThreadMXBean;
  • 预防核心是统一加锁顺序和使用带超时的锁;
  • 工程上更推荐用JUC提供的高级并发工具,减少手动锁的使用。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询