线程死锁:原理、成因与解决方案 线程死锁是多线程编程中常见的隐性问题,指两个或多个线程相互等待对方持有的资源,导致所有线程陷入无限期阻塞的状态。死锁一旦发生,程序往往无法自动恢复,需通过代码设计规避或手动干预解决。
死锁的核心成因 死锁的产生必须同时满足以下四个必要条件 ,缺一不可:
互斥条件 资源具有排他性,同一时间只能被一个线程占用(如synchronized
锁、文件句柄等)。
请求与保持条件 线程持有部分资源,同时又请求其他线程已持有的资源,且不释放自身已占有的资源。
不剥夺条件 线程已获得的资源,在未主动释放前,不能被其他线程强行剥夺(如 Java 的内置锁无法被强制释放)。
循环等待条件 多个线程形成环形等待链,每个线程都在等待下一个线程持有的资源(如线程 A 等线程 B 的资源,线程 B 等线程 A 的资源)。
死锁示例代码 以下是一个典型的死锁场景:两个线程分别持有对方需要的锁,且都不释放自身的锁。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 public class DeadlockExample { private static final Object LOCK_A = new Object(); private static final Object LOCK_B = new Object(); public static void main (String[] args) { new Thread(() -> { synchronized (LOCK_A) { System.out.println("线程1获得LOCK_A,尝试获取LOCK_B..." ); try { Thread.sleep(100 ); } catch (InterruptedException e) { e.printStackTrace(); } synchronized (LOCK_B) { System.out.println("线程1获得LOCK_B,执行完成" ); } } }, "线程1" ).start(); new Thread(() -> { synchronized (LOCK_B) { System.out.println("线程2获得LOCK_B,尝试获取LOCK_A..." ); try { Thread.sleep(100 ); } catch (InterruptedException e) { e.printStackTrace(); } synchronized (LOCK_A) { System.out.println("线程2获得LOCK_A,执行完成" ); } } }, "线程2" ).start(); } }
执行结果 :
1 2 线程1获得LOCK_A,尝试获取LOCK_B... 线程2获得LOCK_B,尝试获取LOCK_A...
程序卡死,两个线程永远无法继续执行。
死锁的检测与定位 在开发和调试阶段,可通过以下工具检测死锁:
JDK 自带工具
jps :查看 Java 进程 ID
jstack :打印线程堆栈,检测死锁
死锁部分堆栈示例:
1 2 3 4 5 6 7 8 Found one Java-level deadlock: ============================= "线程2": waiting to lock monitor 0x00007f8a3a00a888 (object 0x000000076b618610, a java.lang.Object), which is held by "线程1" "线程1": waiting to lock monitor 0x00007f8a3a00d088 (object 0x000000076b618620, a java.lang.Object), which is held by "线程2"
代码层面检测 通过ThreadMXBean
实时监控死锁:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 import java.lang.management.ManagementFactory;import java.lang.management.ThreadMXBean;public class DeadlockDetector { public static void main (String[] args) { ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean(); new Thread(() -> { while (true ) { long [] deadlockedThreadIds = threadMXBean.findDeadlockedThreads(); if (deadlockedThreadIds != null ) { System.out.println("检测到死锁,线程ID:" + Arrays.toString(deadlockedThreadIds)); } try { Thread.sleep(1000 ); } catch (InterruptedException e) { e.printStackTrace(); } } }).start(); } }
死锁的预防与解决 解决死锁的核心思路是破坏四个必要条件中的至少一个 ,具体方案如下:
1.破坏 “循环等待条件”(最常用) 方案 :为所有资源定义统一的获取顺序,线程必须按顺序获取资源,避免环形等待。
优化示例 (修复上述死锁代码):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 public class FixedDeadlockExample { private static final Object LOCK_A = new Object(); private static final Object LOCK_B = new Object(); public static void main (String[] args) { new Thread(() -> { synchronized (LOCK_A) { System.out.println("线程1获得LOCK_A,尝试获取LOCK_B..." ); synchronized (LOCK_B) { System.out.println("线程1获得LOCK_B,执行完成" ); } } }, "线程1" ).start(); new Thread(() -> { synchronized (LOCK_A) { System.out.println("线程2获得LOCK_A,尝试获取LOCK_B..." ); synchronized (LOCK_B) { System.out.println("线程2获得LOCK_B,执行完成" ); } } }, "线程2" ).start(); } }
2.破坏 “请求与保持条件” 方案 :线程获取资源前,先释放已持有的所有资源;或一次性申请所有所需资源,未获取全部则不持有任何资源。
示例 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 public class NoHoldLockExample { private static final Object LOCK_A = new Object(); private static final Object LOCK_B = new Object(); private static boolean tryAcquireBothLocks () { synchronized (LOCK_A) { if (Thread.holdsLock(LOCK_B)) { return true ; } synchronized (LOCK_B) { return true ; } } } public static void main (String[] args) { new Thread(() -> { if (tryAcquireBothLocks()) { System.out.println("线程1获取所有锁,执行完成" ); } }).start(); } }
3.破坏 “不剥夺条件” 方案 :使用可中断的锁(如ReentrantLock
),允许线程在超时或被中断时释放已持有的锁。
示例 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 import java.util.concurrent.locks.Lock;import java.util.concurrent.locks.ReentrantLock;public class InterruptibleLockExample { private static final Lock LOCK_A = new ReentrantLock(); private static final Lock LOCK_B = new ReentrantLock(); public static void main (String[] args) throws InterruptedException { Thread t1 = new Thread(() -> { try { if (LOCK_A.tryLock(1 , TimeUnit.SECONDS)) { try { if (LOCK_B.tryLock(1 , TimeUnit.SECONDS)) { try { System.out.println("线程1获取所有锁,执行完成" ); } finally { LOCK_B.unlock(); } } else { System.out.println("线程1获取LOCK_B超时,释放LOCK_A" ); } } finally { LOCK_A.unlock(); } } } catch (InterruptedException e) { System.out.println("线程1被中断,释放资源" ); } }); t1.start(); } }
4. 破坏 “互斥条件” 方案 :使用可共享的资源(如ReadWriteLock
的读锁),允许多个线程同时访问资源。但该方案仅适用于读多写少的场景,不适用于排他性资源。
v1.3.10