Java 死锁详解:从原理到预防与解决
·
Java 死锁详解:从原理到预防与解决
1. 什么是死锁?
死锁是指两个或两个以上的线程在执行过程中,因争夺资源而造成的一种互相等待的现象。如果没有外部干预,这些线程将无法继续执行下去,程序会陷入永久的阻塞状态。
1.1 生活中的死锁比喻
想象这样一个场景:
- 张三拿着车钥匙,等着李四的手机来打电话
- 李四拿着手机,等着张三的车钥匙去开车
- 两人互相等待,谁也不肯先放手,这就形成了死锁
2. 死锁产生的四个必要条件
死锁的产生必须同时满足以下四个条件:
2.1 互斥条件
一个资源每次只能被一个线程使用。
java
// 比如 synchronized 关键字或 Lock 锁
public synchronized void method() {
// 同一时间只能有一个线程进入
}
2.2 请求与保持条件
一个线程因请求资源而阻塞时,对已获得的资源保持不放。
java
// 线程持有锁A,同时请求锁B
synchronized(lockA) {
synchronized(lockB) {
// ...
}
}
2.3 不剥夺条件
线程已获得的资源,在未使用完之前,不能强行被剥夺。
2.4 循环等待条件
若干线程之间形成一种头尾相接的循环等待资源关系。
java
// 线程1:lockA -> lockB
// 线程2:lockB -> lockA
3. Java 中死锁的代码示例
3.1 经典死锁示例
java
public class ClassicDeadlockExample {
private static final Object lock1 = new Object();
private static final Object lock2 = new Object();
public static void main(String[] args) {
Thread thread1 = new Thread(() -> {
synchronized (lock1) {
System.out.println("Thread1: Holding lock1...");
try {
Thread.sleep(100); // 模拟业务操作
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
System.out.println("Thread1: Waiting for lock2...");
synchronized (lock2) {
System.out.println("Thread1: Acquired lock2!");
}
}
});
Thread thread2 = new Thread(() -> {
synchronized (lock2) {
System.out.println("Thread2: Holding lock2...");
try {
Thread.sleep(100); // 模拟业务操作
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
System.out.println("Thread2: Waiting for lock1...");
synchronized (lock1) {
System.out.println("Thread2: Acquired lock1!");
}
}
});
thread1.start();
thread2.start();
// 等待线程结束
try {
thread1.join();
thread2.join();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
System.out.println("程序执行完毕"); // 这行代码可能永远不会执行
}
}
3.2 使用 ReentrantLock 的死锁示例
java
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class ReentrantLockDeadlockExample {
private static final Lock lock1 = new ReentrantLock();
private static final Lock lock2 = new ReentrantLock();
public static void main(String[] args) {
Thread thread1 = new Thread(() -> {
lock1.lock();
System.out.println("Thread1: Acquired lock1");
try {
Thread.sleep(100); // 模拟业务操作
System.out.println("Thread1: Trying to acquire lock2");
lock2.lock(); // 这里会阻塞,等待lock2
try {
System.out.println("Thread1: Acquired both locks");
} finally {
lock2.unlock();
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
lock1.unlock();
}
});
Thread thread2 = new Thread(() -> {
lock2.lock();
System.out.println("Thread2: Acquired lock2");
try {
Thread.sleep(100); // 模拟业务操作
System.out.println("Thread2: Trying to acquire lock1");
lock1.lock(); // 这里会阻塞,等待lock1
try {
System.out.println("Thread2: Acquired both locks");
} finally {
lock1.unlock();
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
lock2.unlock();
}
});
thread1.start();
thread2.start();
}
}
4. 检测 Java 程序中的死锁
4.1 使用 jstack 工具检测
bash
# 1. 找到 Java 进程的 PID
jps
# 2. 使用 jstack 分析线程状态
jstack <PID>
# 或者一步到位
jstack <PID> | grep -A 10 -B 10 "deadlock"
4.2 编程方式检测死锁
java
import java.lang.management.ManagementFactory;
import java.lang.management.ThreadMXBean;
public class DeadlockDetector {
public static void main(String[] args) {
// 启动死锁检测线程
Thread detectorThread = new Thread(() -> {
while (true) {
checkDeadlock();
try {
Thread.sleep(5000); // 每5秒检测一次
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
break;
}
}
});
detectorThread.setDaemon(true);
detectorThread.start();
// 运行可能产生死锁的代码
ClassicDeadlockExample.main(args);
}
public static void checkDeadlock() {
ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean();
long[] threadIds = threadMXBean.findDeadlockedThreads();
if (threadIds != null) {
System.out.println("检测到死锁!涉及线程:" + threadIds.length);
// 获取线程信息
java.lang.management.ThreadInfo[] threadInfos =
threadMXBean.getThreadInfo(threadIds);
for (java.lang.management.ThreadInfo threadInfo : threadInfos) {
System.out.println("死锁线程: " + threadInfo.getThreadName());
System.out.println("阻塞的锁: " + threadInfo.getLockName());
System.out.println("阻塞的持有者: " + threadInfo.getLockOwnerName());
System.out.println("堆栈跟踪:");
for (StackTraceElement element : threadInfo.getStackTrace()) {
System.out.println(" " + element);
}
System.out.println("---");
}
} else {
System.out.println("未检测到死锁");
}
}
}
5. 预防和解决死锁的策略
5.1 破坏循环等待条件 - 锁顺序化
java
public class LockOrderingSolution {
private static final Object lock1 = new Object();
private static final Object lock2 = new Object();
// 定义锁的获取顺序
private static void acquireLocks(Object firstLock, Object secondLock) {
// 确保总是先获取哈希值较小的锁
Object lockA = firstLock.hashCode() < secondLock.hashCode() ? firstLock : secondLock;
Object lockB = firstLock.hashCode() < secondLock.hashCode() ? secondLock : firstLock;
synchronized (lockA) {
synchronized (lockB) {
// 执行业务逻辑
System.out.println(Thread.currentThread().getName() + " 成功获取两个锁");
}
}
}
public static void main(String[] args) {
Thread thread1 = new Thread(() -> {
acquireLocks(lock1, lock2);
});
Thread thread2 = new Thread(() -> {
acquireLocks(lock2, lock1); // 顺序会被内部方法重新排列
});
thread1.start();
thread2.start();
}
}
5.2 使用 tryLock 避免死锁
java
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class TryLockSolution {
private static final Lock lock1 = new ReentrantLock();
private static final Lock lock2 = new ReentrantLock();
public static void method1() {
while (true) {
if (lock1.tryLock()) {
try {
System.out.println("Thread1: Acquired lock1");
if (lock2.tryLock()) {
try {
System.out.println("Thread1: Acquired both locks - success!");
break; // 成功获取两个锁,退出循环
} finally {
lock2.unlock();
}
}
} finally {
lock1.unlock();
}
}
// 获取失败,稍后重试
try {
Thread.sleep(100 + (int)(Math.random() * 100)); // 随机等待避免活锁
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
break;
}
}
}
public static void method2() {
while (true) {
if (lock2.tryLock()) {
try {
System.out.println("Thread2: Acquired lock2");
if (lock1.tryLock()) {
try {
System.out.println("Thread2: Acquired both locks - success!");
break; // 成功获取两个锁,退出循环
} finally {
lock1.unlock();
}
}
} finally {
lock2.unlock();
}
}
// 获取失败,稍后重试
try {
Thread.sleep(100 + (int)(Math.random() * 100)); // 随机等待避免活锁
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
break;
}
}
}
}
5.3 使用超时机制
java
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class TimeoutLockSolution {
private static final Lock lock1 = new ReentrantLock();
private static final Lock lock2 = new ReentrantLock();
public static boolean tryAcquireLocksWithTimeout() {
boolean acquiredLock1 = false;
boolean acquiredLock2 = false;
try {
// 尝试在指定时间内获取锁
acquiredLock1 = lock1.tryLock(1, TimeUnit.SECONDS);
if (acquiredLock1) {
acquiredLock2 = lock2.tryLock(1, TimeUnit.SECONDS);
if (acquiredLock2) {
return true; // 成功获取两个锁
}
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
// 如果没能获取全部锁,释放已获取的锁
if (acquiredLock1 && !acquiredLock2) {
lock1.unlock();
}
if (acquiredLock2 && !acquiredLock1) {
lock2.unlock();
}
}
return false;
}
}
6. 高级死锁处理模式
6.1 锁分解和锁分段
java
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class LockStrippingExample {
// 传统的粗粒度锁
private final Object coarseLock = new Object();
private int value1, value2;
// 锁分解:为不同资源使用不同的锁
private final Lock lock1 = new ReentrantLock();
private final Lock lock2 = new ReentrantLock();
public void updateValue1(int newValue) {
lock1.lock();
try {
value1 = newValue;
} finally {
lock1.unlock();
}
}
public void updateValue2(int newValue) {
lock2.lock();
try {
value2 = newValue;
} finally {
lock2.unlock();
}
}
// 锁分段:用于HashMap等并发集合
private static final int STRIPES = 16;
private final Lock[] stripes = new ReentrantLock[STRIPES];
public LockStrippingExample() {
for (int i = 0; i < STRIPES; i++) {
stripes[i] = new ReentrantLock();
}
}
private int hash(Object key) {
return Math.abs(key.hashCode() % STRIPES);
}
public void put(String key, String value) {
int stripe = hash(key);
stripes[stripe].lock();
try {
// 执行put操作
System.out.println("Putting " + key + " in stripe " + stripe);
} finally {
stripes[stripe].unlock();
}
}
}
6.2 使用线程池和任务队列
java
import java.util.concurrent.*;
public class ExecutorDeadlockAvoidance {
private static final ExecutorService executor = Executors.newFixedThreadPool(2);
public static void main(String[] args) throws Exception {
// 提交相互依赖的任务,但通过线程池避免死锁
Future<String> future1 = executor.submit(() -> {
System.out.println("Task1 started");
// 模拟长时间运行的任务
Thread.sleep(1000);
return "Result1";
});
Future<String> future2 = executor.submit(() -> {
System.out.println("Task2 started");
// 第二个任务不依赖第一个任务的结果
Thread.sleep(500);
return "Result2";
});
// 获取结果 - 这里不会死锁,因为任务在并行执行
String result1 = future1.get();
String result2 = future2.get();
System.out.println("Results: " + result1 + ", " + result2);
executor.shutdown();
}
}
7. 最佳实践和代码规范
7.1 锁获取顺序规范
java
public class LockOrderingBestPractice {
private final Object resourceA = new Object();
private final Object resourceB = new Object();
private final Object resourceC = new Object();
// 定义明确的锁获取顺序
private static final int ORDER_A = 1;
private static final int ORDER_B = 2;
private static final int ORDER_C = 3;
private void acquireLocksInOrder(Object... locks) {
// 按照预定义顺序排序锁
Arrays.sort(locks, (lock1, lock2) -> {
int order1 = getLockOrder(lock1);
int order2 = getLockOrder(lock2);
return Integer.compare(order1, order2);
});
// 按顺序获取锁
for (Object lock : locks) {
synchronized (lock) {
// 锁已获取
}
}
}
private int getLockOrder(Object lock) {
if (lock == resourceA) return ORDER_A;
if (lock == resourceB) return ORDER_B;
if (lock == resourceC) return ORDER_C;
throw new IllegalArgumentException("Unknown lock: " + lock);
}
}
7.2 使用静态分析工具
在项目中集成死锁检测工具:
xml
<!-- Maven 依赖 -->
<dependency>
<groupId>com.github.spotbugs</groupId>
<artifactId>spotbugs</artifactId>
<version>4.2.2</version>
</dependency>
使用 FindBugs/SpotBugs、PMD 等静态分析工具可以在编译期检测潜在的死锁风险。
8. 总结
死锁是多线程编程中的经典问题,理解其产生原理和解决方法对于编写健壮的并发程序至关重要:
- 理解四个必要条件:互斥、请求与保持、不剥夺、循环等待
- 掌握检测工具:jstack、ThreadMXBean
- 应用预防策略:锁顺序化、tryLock、超时机制
- 遵循最佳实践:锁分解、避免嵌套锁、使用高层并发工具
通过合理的设计和编码规范,可以有效地避免和解决死锁问题,构建高性能、高可用的并发应用程序。
记住:预防死锁的最佳时机是在设计阶段,而不是在出现问题之后!
更多推荐


所有评论(0)