首先我们先简单了解一下什么是死锁
我们模拟A,B是两个资源,而下面是两个要抢占资源的任务
首先左边的任务执行,抢占了A的锁资源
当他想拿继续执行任务,拿B的锁资源的时候,B的资源被右边的任务抢走了
这时候我们应该要了解一下产生死锁的四个必要条件:
四个必要条件
- 一个资源每次只能被一个线程使用
- 一个线程在阻塞等待某资源时,不释放已占有资源
- 一个线程已经获得的资源,在未使用完之前,不能被强行剥夺
- 若干线程形成头尾相接的循环等待资源关系
由这四个条件可知
首先,左右任务各自抢占了一个资源,以左为例,首先会想到继续拿资源B,但是根据上面的条件1,3可以知道,资源不能多路复用,也不能强行剥夺
这时,以人的思维来讲,应该退让一步,把A资源也给让出去,但是由于条件2可以得知,线程并不会这么做,他拿不到资源B就会一直把资源A握在手里,陷入了进退两难的地步,而右边的任务同样面临着这样的境地
最后,他们等待抢夺资源形成了一个闭环,这个闭环不一定是两个,也可能是多个,如
他们构成了头尾相接的循环等待,满足了第四个条件,这样一个标准的死锁就诞生哩!
死锁是一个开发的事故,他不能解决,只能尽可能的去避免
如何避免死锁:
其实很简答,造成死锁必须要满足上面的四个条件,而我们只要在开发过程中,破坏掉任意一个条件,就可以避免死锁
而其中前三个条件作为锁要符合的条件,所以避免死锁就需要打破第四个条件,不出现循环等待的锁的关系
在开发过程中:
- 需要注意加锁顺序,保证每个线程按照同样的顺序进行加锁
如:
public class DeadlockExample {
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("Thread 1: Holding lock1...");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Thread 1: Waiting for lock2...");
synchronized (lock2) {
System.out.println("Thread 1: Acquired lock2.");
}
}
});
Thread thread2 = new Thread(() -> {
synchronized (lock1) {
System.out.println("Thread 2: Holding lock1...");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Thread 2: Waiting for lock2...");
synchronized (lock2) {
System.out.println("Thread 2: Acquired lock2.");
}
}
});
thread1.start();
thread2.start();
}
}
两个线程都按照先锁lock1,再锁lock2的顺序执行,就可以避免陷入循环
- 要注意加锁时限,可以针对锁设置一个超时时间
public class LockTimeoutExample {
private static final Lock lock = new ReentrantLock();
public static void main(String[] args) {
try {
if (lock.tryLock(5, TimeUnit.SECONDS)) {
try {
System.out.println("Acquired the lock.");
// Perform your critical section work here
} finally {
lock.unlock();
System.out.println("Released the lock.");
}
} else {
System.out.println("Could not acquire the lock within the timeout.");
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
在上面的例子中,我们使用了ReentrantLock作为锁,并在tryLock()方法中设置了超时时间为5秒。如果在5秒内成功获取了锁,则可以执行临界区代码,然后在结束后释放锁。如果在5秒内无法获取锁,就会执行相应的超时操作。
比如我抢到了A,在抢B时设置一个时间,如果超过了这个时间就不抢B了,然后顺手也给A放了,这样也能很好的避免死锁
- 要注意死锁检查,这是一种预防机制,确保第一时间发现死锁并且解决
import java.lang.management.ManagementFactory;
import java.lang.management.ThreadInfo;
import java.lang.management.ThreadMXBean;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class DeadlockDetectionExample {
private static final Lock lock1 = new ReentrantLock();
private static final Lock lock2 = new ReentrantLock();
public static void main(String[] args) throws InterruptedException {
Thread thread1 = new Thread(() -> {
synchronized (lock1) {
System.out.println("Thread 1: Holding lock1...");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Thread 1: Waiting for lock2...");
synchronized (lock2) {
System.out.println("Thread 1: Acquired lock2.");
}
}
});
Thread thread2 = new Thread(() -> {
synchronized (lock2) {
System.out.println("Thread 2: Holding lock2...");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Thread 2: Waiting for lock1...");
synchronized (lock1) {
System.out.println("Thread 2: Acquired lock1.");
}
}
});
thread1.start();
thread2.start();
Thread.sleep(2000);
ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean();
long[] deadlockedThreadIds = threadMXBean.findDeadlockedThreads();
if (deadlockedThreadIds != null) {
System.out.println("Deadlock detected. Taking corrective action...");
ThreadInfo[] threadInfos = threadMXBean.getThreadInfo(deadlockedThreadIds);
for (ThreadInfo threadInfo : threadInfos) {
System.out.println("Thread ID: " + threadInfo.getThreadId());
System.out.println("Thread Name: " + threadInfo.getThreadName());
System.out.println("Thread State: " + threadInfo.getThreadState());
System.out.println("Blocked Time: " + threadInfo.getBlockedTime());
System.out.println("Lock Name: " + threadInfo.getLockName());
System.out.println("Lock Owner ID: " + threadInfo.getLockOwnerId());
System.out.println("Lock Owner Name: " + threadInfo.getLockOwnerName());
System.out.println();
}
for (long threadId : deadlockedThreadIds) {
Thread thread = threadMXBean.getThreadInfo(threadId).getThread();
if (thread != null) {
thread.interrupt();
}
}
}
}
}
在这个案例中,我们创建了两个线程,并故意设计了一个死锁情况。然后,我们使用ThreadMXBean来检测死锁。如果死锁被检测到,我们打印出有关死锁线程的信息,并采取措施来解决死锁,如中断线程。
需要注意的是,死锁检测和解决可能会涉及到复杂的逻辑和操作,具体的处理方法取决于实际情况。在实际应用中,你可能需要根据检测到的死锁情况来采取不同的措施,以确保系统的稳定性和可靠性。