Deadlock and its solution in Java

Deadlock and its solution in Java

In Java, lock is a very common tool. The common usage is to ensure thread safety in high concurrency scenarios, but improper use can also cause deadlock, which brings us some unnecessary troubles. This article analyzes deadlock and its Causes and corresponding solutions.

Four necessary conditions for deadlock to occur

1) Mutually exclusive conditions: Processes have exclusive control over allocated resources, that is, a resource is only occupied by one process within a period of time. At this time, if other processes request the resource, the requesting process can only wait.

2) Request and hold conditions: the process has obtained at least one resource, but it sends a request for other resources, and the resource is already occupied by other processes. At this time, the request of the process is blocked, but the resource obtained by itself remains unchanged. put.

3) Non-alienable conditions: Before the resources obtained by the process are used up, they cannot be forcibly deprived by other processes, but can only be released by themselves.

4) Circular waiting condition: There is a circular waiting chain of process resources, and the resource obtained by each process in the chain is simultaneously requested by the next process in the chain.

life example

A deadlock is like two people going to a single-plank bridge. Xiao Pang starts from the left side of the bridge, Xiao Jia starts from the right side of the bridge, and then reaches the middle of the bridge. You can only stand in the middle of the bridge, resulting in an endless loop, and no one can cross the river.

Object A first holds lock 1, and then object B holds lock 2. At this time, object A wants to acquire lock 2, but lock 2 is in the hands of object B at this time, object B wants to acquire lock 1, and lock 1 is in object A In the hands, the objects A and B do not give in to each other, which eventually leads to a dead chain phenomenon and a deadlock.

deadlock code

Method 1, inheriting the implementation of the Runnable interface
package com.wwy.lock;

import java.util.concurrent.TimeUnit;

public class DieLock {
    public static void main(String[] args) {


         Object A = new Object();

         Object B = new Object();

         //thread 1
        /**
         * ()->{} This is a new feature added to jdk8, but requires the target interface to have only one abstract method, and Runnable just meets this condition
         */
        Thread thread1 = new Thread(()->{
            synchronized (A){
                System.out.println("Thread 1 acquires the lock at this time A");
                try {
                    /**
                     * TimeUnit It is a time tool class, which can better help us define sleep time
                     *TimeUnit.DAYS //sky
                     * TimeUnit.HOURS //Hour
                     * TimeUnit.MINUTES //minute
                     * TimeUnit.SECONDS //Second
                     * TimeUnit.MILLISECONDS //millisecond
                     * TimeUnit.NANOSECONDS //nanosecond
                     * TimeUnit.MICROSECONDS //microsecond
                     */
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                //After object A wakes up, try to acquire lock 2
                synchronized (B){
                    System.out.println("Thread 1 acquires the lock at this time B");
                }
            }
        });

        thread1.start();
        //thread 2
        Thread thread2 = new Thread(()->{
            synchronized (B){
                System.out.println("Thread 2 acquires the lock at this time B");
                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                //After object A wakes up, try to acquire lock 2
                synchronized (A){
                    System.out.println("Thread 2 acquires the lock at this time A");
                }
            }
        });

        thread2.start();
    }

}

Method 2, inheriting the Thread class (not recommended)
package com.wwy.lock;

import java.util.concurrent.TimeUnit;

public class ThreadA extends Thread {
    private Object A ;
    private Object B ;

    public Object getA() {
        return A;
    }

    public void setA(Object a) {
        A = a;
    }

    public Object getB() {
        return B;
    }

    public void setB(Object b) {
        B = b;
    }

    @Override
    public void run() {
        synchronized (A){
            System.out.println("Thread 1 acquires the lock at this time A");
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            synchronized (B){
                System.out.println("Thread 1 acquires the lock at this time B");
            }
        }
    }
}
package com.wwy.lock;

import java.util.concurrent.TimeUnit;

public class ThreadB extends Thread {

   private Object A ;
   private Object B ;

    public Object getA() {
        return A;
    }

    public void setA(Object a) {
        A = a;
    }

    public Object getB() {
        return B;
    }

    public void setB(Object b) {
        B = b;
    }

    @Override
    public void run() {
        synchronized (B){
            System.out.println("Thread 2 acquires the lock at this time B");
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            synchronized (A){
                System.out.println("Thread 2 acquires the lock at this time A");
            }
        }
    }
}
package com.wwy.lock;

public class ThreadTest  {
    public static void main(String[] args) {

        Object A = new Object();
        Object B = new Object();
        ThreadA t1 = new ThreadA();
        ThreadB t2 = new ThreadB();
        t1.setA(A);
        t1.setB(B);
        t2.setA(A);
        t2.setB(B);
        t1.start();
        t2.start();
    }
}

Detect deadlock

Under normal circumstances, four sentences should be printed, but at this time the program has been running and only printed two sentences, indicating that there is a deadlock phenomenon. For the verification method, you can go to a visual interface in the bin directory under our jdk directory Check

It can be seen that there is indeed a deadlock phenomenon here

deadlock solution

The way to prevent deadlock is to break any one of the four necessary conditions.

1) Break the mutual exclusion condition: cancel the mutual exclusion in the system. If the resource is not exclusively used by one process, then deadlock will definitely not happen. But in general, among the four conditions listed, the "mutually exclusive" condition cannot be broken. Therefore, in deadlock prevention, it is mainly to destroy several other necessary conditions, not to involve the destruction of "mutual exclusion" conditions. .

2) Break the request and hold conditions: adopt the resource pre-allocation strategy, that is, apply for all resources before the process runs, run if it is satisfied, or wait if it is satisfied.

Before each process makes a new resource application, it must release the resources it previously occupied.

3) Break the inalienable condition: When a process occupies certain resources and further applies for other resources but cannot be satisfied, the process must release the resources it originally occupied.

4) Break the loop waiting condition: implement an orderly resource allocation strategy, and uniformly number all resources in the system, and all processes can only apply for resources in the form of increasing serial numbers.

Solution 1, change the order in which two threads acquire locks

The order in which thread 1 and thread 2 acquire locks is to first acquire lock A and then acquire lock B

package com.wwy.lock;

import java.util.concurrent.TimeUnit;

public class DieLock {
    public static void main(String[] args) {


         Object A = new Object();

         Object B = new Object();

         //thread 1
        /**
         * ()->{} This is a new feature added to jdk8, but requires the target interface to have only one abstract method, and Runnable just meets this condition
         */
        Thread thread1 = new Thread(()->{
            synchronized (A){
                System.out.println("Thread 1 acquires the lock at this time A");
                try {
                    /**
                     * TimeUnit It is a time tool class, which can better help us define sleep time
                     *TimeUnit.DAYS //sky
                     * TimeUnit.HOURS //Hour
                     * TimeUnit.MINUTES //minute
                     * TimeUnit.SECONDS //Second
                     * TimeUnit.MILLISECONDS //millisecond
                     * TimeUnit.NANOSECONDS //nanosecond
                     * TimeUnit.MICROSECONDS //microsecond
                     */
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                //After object A wakes up, try to acquire lock 2
                synchronized (B){
                    System.out.println("Thread 1 acquires the lock at this time B");
                }
            }
        });

        thread1.start();
        //thread 2
        Thread thread2 = new Thread(()->{
            synchronized (A){
                System.out.println("Thread 2 acquires the lock at this time A");
                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                //After object A wakes up, try to acquire lock 2
                synchronized (B){
                    System.out.println("Thread 2 acquires the lock at this time B");
                }
            }
        });

        thread2.start();
    }



}

The same is true for inheriting the Runnable class, changing the order in which two threads acquire locks

Method 2, use ReentrantLock to solve
package com.wwy.lock;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;

public class DieLock {
    public static void main(String[] args) {


        ReentrantLock lockA = new ReentrantLock();

        ReentrantLock lockB = new ReentrantLock();

         //thread 1
        /**
         * ()->{} This is a new feature added to jdk8, but requires the target interface to have only one abstract method, and Runnable just meets this condition
         */
        Thread thread1 = new Thread(()->{
            while(true) {
                if (lockA.tryLock()) {
                    System.out.println("Thread 1 acquires the lock A");
                    try {
                        TimeUnit.SECONDS.sleep(1);
                        if (lockB.tryLock()) {
                            try {
                                System.out.println("Thread 1 acquires the lock B");
                            } finally {
                                lockB.unlock();
                                System.out.println("Thread 1 releases the lock B");
                                break;
                            }

                        }
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    } finally {
                        /**
                         * ReentrantLock The difference from synchronized is that the lock that needs to be manually released to acquire
                         */
                        lockA.unlock();
                        System.out.println("Thread 1 releases the lock A");
                    }

                }
                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }

        });

        thread1.start();
        //thread 2
        Thread thread2 = new Thread(()->{
            /**
             * lock.lock()If the method fails to acquire the lock, the thread is disabled and remains in a dormant state until the lock is acquired
             */
            lockB.lock();
                System.out.println("Thread 2 acquires the lock B");
                try {
                    TimeUnit.SECONDS.sleep(1);
                    lockA.lock();
                        try {
                            System.out.println("Thread 2 acquires the lock A");
                        }finally {
                            lockA.unlock();
                            System.out.println("Thread 2 releases the lock A");
                        }

                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                finally {
                    /**
                     * ReentrantLock The difference from synchronized is that the lock that needs to be manually released to acquire
                     */
                    lockB.unlock();
                    System.out.println("Thread 2 releases the lock B");
                }

        });

        thread2.start();
    }



}

Tags: Java

Posted by anakadote on Thu, 02 Mar 2023 20:47:44 +1030