前言
死锁是并发编程中的常见问题,它发生在两个或多个线程被阻塞,等待对方释放锁时。死锁可能导致整个系统冻结或崩溃,是一个难以复现和修复的问题。在本文中,我们将探讨 Java 中死锁的成因、检测方法以及避免死锁的最佳实践。
什么是死锁?
Java中的死锁是当两个或多个线程被阻塞并等待对方释放资源,这种情况叫做死锁。换句话说,两个或多个线程被卡住而无法继续,因为每个线程都持有另一个线程所需的资源,从而导致循环依赖。这可能会导致系统完全冻结或崩溃。
例如,考虑两个线程,线程 A 和线程 B,以及两个锁,锁 1 和锁 2。线程 A 获取锁 1,线程 B 获取锁 2。但是,线程 A 需要锁 2 才能继续,而线程 B 需要 锁 1 才能继续执行,该锁正被线程 A 持有。这导致循环依赖,两个线程都被阻塞并等待另一个线程释放锁。这种情况称为死锁。
我们直接看一个代码:
package core.multithreading;
public class DeadlockExample {
public static Object lock1 = new Object();
public static Object lock2 = new Object();
public static void main(String[] args) {
Thread threadA = new Thread(() -> {
synchronized(lock1) {
System.out.println("Thread A acquired lock 1");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {}
synchronized(lock2) {
System.out.println("Thread A acquired lock 2");
}
}
});
Thread threadB = new Thread(() -> {
synchronized(lock2) {
System.out.println("Thread B acquired lock 2");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {}
synchronized(lock1) {
System.out.println("Thread B acquired lock 1");
}
}
});
threadA.start();
threadB.start();
}
}
在这个例子中,我们有两个线程,threadA
和 threadB
,它们都访问两个锁,lock1
和 lock2
。threadA
先获得lock1
,然后threadB
获得lock2
,两个线程都休眠一秒。然后threadA
尝试获取threadB
持有的lock2
,threadB
尝试获取threadA
持有的lock1
。这导致循环依赖,两个线程都被阻塞并等待另一个线程释放锁,从而导致死锁。
为了避免这样的死锁,您可以遵循并发编程的最佳实践,例如以固定顺序获取锁、在获取锁时使用超时、最小化锁的范围以及使用juc包中的ReentrantLock
。