【Java】产生死锁的必要条件和如何避免死锁

首先我们先简单了解一下什么是死锁

我们模拟A,B是两个资源,而下面是两个要抢占资源的任务
在这里插入图片描述
首先左边的任务执行,抢占了A的锁资源
在这里插入图片描述
当他想拿继续执行任务,拿B的锁资源的时候,B的资源被右边的任务抢走了
在这里插入图片描述
这时候我们应该要了解一下产生死锁的四个必要条件:

四个必要条件

  1. 一个资源每次只能被一个线程使用
  2. 一个线程在阻塞等待某资源时,不释放已占有资源
  3. 一个线程已经获得的资源,在未使用完之前,不能被强行剥夺
  4. 若干线程形成头尾相接的循环等待资源关系

由这四个条件可知
首先,左右任务各自抢占了一个资源,以左为例,首先会想到继续拿资源B,但是根据上面的条件1,3可以知道,资源不能多路复用,也不能强行剥夺
这时,以人的思维来讲,应该退让一步,把A资源也给让出去,但是由于条件2可以得知,线程并不会这么做,他拿不到资源B就会一直把资源A握在手里,陷入了进退两难的地步,而右边的任务同样面临着这样的境地
最后,他们等待抢夺资源形成了一个闭环,这个闭环不一定是两个,也可能是多个,如
在这里插入图片描述
他们构成了头尾相接的循环等待,满足了第四个条件,这样一个标准的死锁就诞生哩!

死锁是一个开发的事故,他不能解决,只能尽可能的去避免

如何避免死锁:

其实很简答,造成死锁必须要满足上面的四个条件,而我们只要在开发过程中,破坏掉任意一个条件,就可以避免死锁

而其中前三个条件作为锁要符合的条件,所以避免死锁就需要打破第四个条件,不出现循环等待的锁的关系

在开发过程中:

  1. 需要注意加锁顺序,保证每个线程按照同样的顺序进行加锁
    如:
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的顺序执行,就可以避免陷入循环

  1. 要注意加锁时限,可以针对锁设置一个超时时间
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放了,这样也能很好的避免死锁

  1. 要注意死锁检查,这是一种预防机制,确保第一时间发现死锁并且解决
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来检测死锁。如果死锁被检测到,我们打印出有关死锁线程的信息,并采取措施来解决死锁,如中断线程。

需要注意的是,死锁检测和解决可能会涉及到复杂的逻辑和操作,具体的处理方法取决于实际情况。在实际应用中,你可能需要根据检测到的死锁情况来采取不同的措施,以确保系统的稳定性和可靠性。

猜你喜欢

转载自blog.csdn.net/qq_67548292/article/details/132226062
今日推荐