Appearance
06_死锁
两个或多个线程互相持有对方所需的资源,导致线程无法继续执行,称为死锁。死锁是多线程编程中常见的问题,也是非常难以调试和解决的问题。
1. 死锁的案例
java
/**
* 死锁案例
* t1.锁住lock1,等待lock2
* t2.锁住lock2,等待lock1
*/
public class DeadLockDemo {
private static final Object lock1 = new Object();
private static final Object lock2 = new Object();
public static void main(String[] args) {
Thread t1 = new Thread(() -> {
synchronized (lock1) {
System.out.println("Thread 1: Holding lock 1...");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("Thread 1: Waiting for lock 2...");
synchronized (lock2) {
System.out.println("Thread 1: Holding lock 1 & 2...");
}
}
});
Thread t2 = new Thread(() -> {
synchronized (lock2) {
System.out.println("Thread 2: Holding lock 2...");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("Thread 2: Waiting for lock 1...");
synchronized (lock1) {
System.out.println("Thread 2: Holding lock 1 & 2...");
}
}
});
t1.start();
t2.start();
}
}
2. 产生死锁的原因
- 系统资源不足
- 进程运行推进顺序不合适
- 资源分配不当
3. 避免死锁的方法
- 避免一个线程同时获取多个锁
- 避免一个线程在锁内同时占用多个资源,尽量保证每个锁只占用一个资源
- 尝试使用定时锁,使用
lock.tryLock(timeout)
来替代内部锁机制 - 对于数据库锁,加锁和解锁必须在一个数据库事务中,否则会出现死锁
- 要注意加锁顺序,保证每个线程按同样的加锁顺序.
- 要注意加锁时限,针对锁设置一个加锁时限.
- 要注意死锁检查,确保在第一时间发现死锁并解决.
4. 死锁的检测
- 使用
jps
命令查看Java进程ID - 使用
jstack 进程ID
命令查看线程堆栈信息 - 或者使用
jconsole
或jvisualvm
查看线程堆栈信息
5. 死锁的解决
- 通过分析死锁的原因,找到导致死锁的根本原因
- 通过调整线程的运行顺序,避免死锁的发生
- 通过增加资源,避免死锁的发生
- 通过减少资源的占用,避免死锁的发生
- 通过增加超时时间,避免死锁的发生
终止某个线程
- 终止某个线程是最简单的恢复死锁的方法,但是需要谨慎使用。当一个线程被终止时,可能会导致程序异常或数据不一致等问题。
回滚一定的步数
- 回滚一定的步数是指,当发生死锁时,回滚已经获取的资源,然后重新获取资源。这种方法可以避免终止线程可能带来的问题,但是需要保证回滚和重新获取资源的操作是原子性的。