[Java] Necessary conditions for deadlock and how to avoid deadlock

First, let’s briefly understand what deadlock is

We simulate A and B as two resources, and the following are two tasks that want to seize resources.
Insert image description here
First, the task on the left is executed and seizes A's lock resources.
Insert image description here
When he wants to continue executing the task and take B's lock resources, B's The resource was snatched away by the task on the right.
Insert image description here
At this time, we should understand the four necessary conditions for deadlock:

Four necessary conditions

  1. A resource can only be used by one thread at a time
  2. When a thread is blocked waiting for a resource, it does not release the occupied resource.
  3. Resources that a thread has obtained cannot be forcibly deprived of them before they are used up.
  4. Several threads form a head-to-tail cycle waiting for resources.

It can be seen from these four conditions that
first, the left and right tasks each seize a resource. Taking Zuo as an example, the first thing you will think of is to continue to take resource B. However, according to the above conditions 1 and 3, you can know that resources cannot be multiplexed or forced. At this time of deprivation
, in terms of human thinking, we should take a step back and give away resource A. However, we can know from condition 2 that the thread will not do this. If it cannot get resource B, it will keep giving up resource A. Holding it in their hands, they are in a dilemma, and the tasks on the right are also facing this situation.
In the end, they wait to snatch the resources to form a closed loop. This closed loop may not necessarily be two, but may also be multiple. For example,
Insert image description here
they form The end-to-end loop waiting satisfies the fourth condition, and a standard deadlock is born!

Deadlock is a development accident that cannot be solved but can only be avoided as much as possible.

How to avoid deadlock:

In fact, the answer is very simple. The above four conditions must be met to cause deadlock. As long as we destroy any one of the conditions during the development process, we can avoid deadlock.

The first three conditions are the conditions that the lock must meet, so to avoid deadlock, you need to break the fourth condition and avoid the relationship of cyclic waiting for locks.

During development:

  1. It is necessary to pay attention to the locking order to ensure that each thread is locked in the same order.
    For example:
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();
    }
}

Both threads execute in the order of first locking lock1 and then locking lock2 to avoid falling into a loop.

  1. Pay attention to the locking time limit. You can set a timeout for the lock.
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();
        }
    }
}

In the above example, we used ReentrantLock as the lock and set the timeout to 5 seconds in the tryLock() method. If the lock is successfully acquired within 5 seconds, you can execute the critical section code and then release the lock when it is finished. If the lock cannot be obtained within 5 seconds, the corresponding timeout operation will be performed.
For example, if I grab A, I set a time when I grab B. If it exceeds this time, I won't grab B, and then I also release it to A. This can also avoid deadlock very well.

  1. Pay attention to deadlock checking, which is a preventive mechanism to ensure that deadlocks are discovered and resolved as soon as possible
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(); 
                }
            }
        }
    }
}

In this case, we created two threads and deliberately designed a deadlock situation. Then, we use ThreadMXBean to detect deadlock. If a deadlock is detected, we print out information about the deadlocked thread and take steps to resolve the deadlock, such as interrupting the thread.

It should be noted that deadlock detection and resolution may involve complex logic and operations, and the specific processing method depends on the actual situation. In actual applications, you may need to take different measures based on the detected deadlock situation to ensure the stability and reliability of the system.

Guess you like

Origin blog.csdn.net/qq_67548292/article/details/132226062