Deadlock and Prevention

1. Four conditions for deadlock

Deadlock (the original concept of deadlock was proposed in multi-process mode, and the description of threads here means the same) is a difficult problem in multi-threaded concurrent programs. To generate deadlock, the following four conditions must be met:

  1. Mutual exclusion conditions for limited resources: resources can only be occupied by threads, and can only be occupied by other threads after they are released, such as CD-ROM drives, printers, etc. This is determined by the attribute lock of the resource itself. A single-plank bridge is an exclusive resource in real life.
  2. Occupied resources cannot be preempted: other applicants cannot preempt resources before a thread has finished using the resources, and can only wait for the resource occupier to release it voluntarily.
  3. Occupied resources and apply for other mutually exclusive resources: A thread has occupied a mutually exclusive resource, and then applies for a mutually exclusive resource occupied by other threads.
  4. Circular waiting: Multithreading waits in a circular manner for mutually exclusive resources that have been occupied by other threads.

Second, the prevention of deadlock

Consider from the perspective of four conditions for destroying deadlock:

   <1> Break the mutually exclusive condition. That is, a process is allowed to access certain resources at the same time. However, some resources are not allowed to be accessed at the same time, such as printers, etc., which are determined by the properties of the resources themselves. Therefore, this approach has no practical value.

   <2> Break the non-preemption condition. That is, a process is allowed to forcibly take certain resources from the occupant. That is to say, when a process has occupied some resources and it applies for new resources, but cannot be satisfied immediately, it must release all occupied resources and apply again later. The resources it releases can be allocated to other processes. This is equivalent to the resources occupied by the process are covertly occupied. This method of preventing deadlocks is difficult to implement and reduces system performance.    

    <3> Break possession and application conditions. A resource pre-allocation strategy can be implemented. That is, the process applies to the system for all the resources it needs at one time before running. If all the resources required by a process cannot be satisfied, no resources will be allocated and the process will not run temporarily. Only when the system can meet all the resource requirements of the current process, will all the requested resources be allocated to the process at one time. Since the running process has occupied all the resources it needs, the phenomenon of occupying resources and applying for resources will not occur, so deadlock will not occur. However, this strategy also has the following disadvantages:

(1) In many cases, it is impossible for a process to know all the resources it needs before executing it. This is due to the fact that the process is dynamic and unpredictable during execution;

(2) The resource utilization rate is low. Regardless of when the allocated resources are used, a process cannot execute until it has all the resources it needs. Even though some resources are only used once by the process, the process keeps occupying them during the lifetime, resulting in a long-term occupied situation. This is obviously a huge waste of resources;

(3) Reduce the concurrency of the process. Because of limited resources and waste, the number of processes that can allocate all the required resources is bound to be small.    

(4) Break the cyclic waiting condition and implement the strategy of orderly allocation of resources. Using this strategy, the resources are classified and numbered in advance and allocated according to the number, so that the process will not form a loop when applying for and occupying resources. All process requests for resources must be made in strict order of increasing resource sequence numbers. A process can apply for a large resource only after occupying a small resource, and no loop will be generated, thus preventing a deadlock. Compared with the previous strategy, this strategy greatly improves resource utilization and system throughput, but also has the following disadvantages:

(1) It limits the process's request for resources, and it is also difficult to reasonably number all resources in the system, and increases the system overhead;

(2) In order to follow the order of application by number, resources that are not used for the time being also need to be applied for in advance, thus increasing the time occupied by the process for resources.

Three, Java deadlock paradigm analysis and avoidance

package com.journaldev.threads;
 
public class ThreadDeadlock {
 
    public static void main(String[] args) throws InterruptedException {
        Object obj1 = new Object();
        Object obj2 = new Object();
        Object obj3 = new Object();
 
        Thread t1 = new Thread(new SyncThread(obj1, obj2), "t1");
        Thread t2 = new Thread(new SyncThread(obj2, obj3), "t2");
        Thread t3 = new Thread(new SyncThread(obj3, obj1), "t3");
 
        t1.start();
        Thread.sleep(5000);
        t2.start();
        Thread.sleep(5000);
        t3.start();
 
    }
 
}
 
class SyncThread implements Runnable{
    private Object obj1;
    private Object obj2;
 
    public SyncThread(Object o1, Object o2){
        this.obj1=o1;
        this.obj2=o2;
    }
    @Override
    public void run() {
        String name = Thread.currentThread().getName();
        System.out.println(name + " acquiring lock on "+obj1);
        synchronized (obj1) {
         System.out.println(name + " acquired lock on "+obj1);
         work();
         System.out.println(name + " acquiring lock on "+obj2);
         synchronized (obj2) {
            System.out.println(name + " acquired lock on "+obj2);
            work();
        }
         System.out.println(name + " released lock on "+obj2);
        }
        System.out.println(name + " released lock on "+obj1);
        System.out.println(name + " finished execution.");
    }
    private void work() {
        try {
            Thread.sleep(30000);
        } catch (InterruptedException e) {
            e.printStackTrace ();
        }
    }
}

Deadlock analysis, through the thread snapshot generated by jstack, you can clearly find deadlock threads and their causes.

Methods to avoid deadlocks in Java coding:

  • Avoid nested blocking: This is the main reason for deadlocks, if you already have a resource avoid blocking another resource. If you only have one object blocked at runtime, it's almost impossible to have a deadlock situation. For example, here is another run() method that runs without nested blockades, and the program runs successfully without deadlock situations.
  • Block only the requested ones: You should only get blocked for the resources you want to run, such as the complete object resources I blocked in the above program. But if we are only interested in one of its fields, then we should block that particular field and not the full object.
  • Avoid waiting indefinitely: If two threads are waiting for the object to end, use the thread to join indefinitely. If your thread has to wait for the end of another thread, it is best to prepare for the longest time if waiting for the end of the process to join.
  • When using lock, release the lock in the correct place such as in the finally block.

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=324975742&siteId=291194637