This is the 7th day of my participation in the August Text Challenge.More challenges in August

A Dead Lock is when two or more units (processes, threads, or coroutines) are waiting for each other to stop executing in order to obtain system resources, but neither of them quits early.Deadlock example code is as follows:

public class DeadLockExample {
    public static void main(String[] args) {
        Object lockA = new Object(); // Create lock A
        Object lockB = new Object(); // Create lock B

        // Create thread 1
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run(a) {
                synchronized (lockA) {
                    System.out.println("Thread 1: got lock A!");
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println("Thread 1: Waiting for B...");
                    synchronized (lockB) {
                        System.out.println("Thread 1: acquired lock B!"); }}}}); t1.start();// Run the thread

        // Create thread 2
        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run(a) {
                synchronized (lockB) {
                    System.out.println("Thread 2: Acquired lock B!");
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println("Thread 2: Waiting for A...");
                    synchronized (lockA) {
                        System.out.println("Thread 2: Got lock A!"); }}}}); t2.start();// Run the thread}}Copy the code

The execution results of the above procedures are as follows:As you can see from the above results, thread 1 and thread 2 are both in a deadlock state, each waiting for the other to release the lock.

From the above example analysis, the following four conditions must be met to generate a deadlock:

  1. Mutual exclusion: An operation unit (process, thread, or coroutine) is exclusive to allocated resources, that is, a locked resource can only be occupied by one operation unit at a time.
  2. Request and hold condition: the operation unit has held at least one resource, but it makes a new resource request, and the resource has been occupied by another operation unit. In this case, the requesting operation unit blocks, but it does not release the other resources it has obtained.
  3. Inalienable condition: resources acquired by the operation unit cannot be deprived until they are used up.
  4. Loop waiting condition: when a deadlock occurs, there must be a ring chain between an operation unit and a resource. That is, an operation unit is waiting for resources occupied by another operation unit, while the other operation unit is waiting for resources occupied by itself, causing loop waiting.

Only if all four conditions are met can deadlock be a problem.

So, in order to create a deadlock, all four conditions must be met, and we can solve the deadlock problem by breaking any one of them.

Deadlock solution analysis

Now let’s analyze the four conditions for deadlock. Which ones can be broken? What can’t be destroyed?

  • Mutually exclusive: A system feature that cannot be destroyed.
  • Request and hold conditions: can be broken.
  • Inalienable conditions: System characteristics that cannot be destroyed.
  • Loop wait condition: Can be broken.

From the above analysis, we can conclude that we can only solve the deadlock problem by breaking the request and hold conditions or loop wait conditions, so we can solve the deadlock problem by breaking the loop wait conditions first.

Solution 1: Sequential locking

The so-called sequential lock is to obtain the lock sequentially, so as to avoid loop waiting conditions, so as to solve the deadlock problem.

When we do not use sequential locking, the program execution might look like this:Thread 1 first acquires lock A, then acquires lock B, thread 2 and thread 1 execute simultaneously, thread 2 acquires lock B first, then acquires lock A, so both parties occupy their own resources (lock A and lock B), then try to acquire the lock of the other party, thus causing the loop waiting problem, and finally causing the deadlock problem.

At this point, we only need to unify the lock acquisition sequence of thread 1 and thread 2, that is, after thread 1 and thread 2 execute simultaneously, they both acquire lock A first and then lock B, as shown in the following figure:Because only one thread can access to lock A success, there is no access to lock A thread will wait for acquiring A lock A, first time get A thread to continue acquiring A lock lock B, because there is no thread for and lock B, then get A thread lock will be smooth with lock B, after the execution of corresponding code lock resources to be released in full, Then another thread waiting to acquire lock A can successfully acquire the lock resource and execute the following code so that the deadlock problem does not occur.

The code for sequential locking is as follows:

public class SolveDeadLockExample {
    public static void main(String[] args) {
        Object lockA = new Object(); // Create lock A
        Object lockB = new Object(); // Create lock B
        // Create thread 1
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run(a) {
                synchronized (lockA) {
                    System.out.println("Thread 1: got lock A!");
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println("Thread 1: Waiting for B...");
                    synchronized (lockB) {
                        System.out.println("Thread 1: acquired lock B!"); }}}}); t1.start();// Run the thread
        // Create thread 2
        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run(a) {
                synchronized (lockA) {
                    System.out.println("Thread 2: Got lock A!");
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println("Thread 2: Waiting for B...");
                    synchronized (lockB) {
                        System.out.println("Thread 2: Acquired lock B!"); }}}}); t2.start();// Run the thread}}Copy the code

The execution results of the above procedures are as follows:As can be seen from the above execution results, the program does not have a deadlock problem.

Solution 2: Polling locks

Polling locks avoid deadlocks by breaking the “request and hold condition”. Its implementation idea is simply to try to obtain locks through polling. If one lock fails to obtain, all the locks owned by the current thread are released and the next round is to try to obtain the lock.

The implementation of polling lock needs to use the tryLock method of ReentrantLock, the specific implementation code is as follows:

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

public class SolveDeadLockExample {
    
    public static void main(String[] args) {
        Lock lockA = new ReentrantLock(); // Create lock A
        Lock lockB = new ReentrantLock(); // Create lock B

        // Create thread 1(using polling lock)
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run(a) {
                // Call the polling lockpollingLock(lockA, lockB); }}); t1.start();// Run the thread

        // Create thread 2
        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run(a) {
                lockB.lock(); / / lock
                System.out.println("Thread 2: Acquired lock B!");
                try {
                    Thread.sleep(1000);
                    System.out.println("Thread 2: Waiting for A...");
                    lockA.lock(); / / lock
                    try {
                        System.out.println("Thread 2: Got lock A!");
                    } finally {
                        lockA.unlock(); / / releases the lock}}catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    lockB.unlock(); / / releases the lock}}}); t2.start();// Run the thread
    }
    
     /** * polling lock */
    public static void pollingLock(Lock lockA, Lock lockB) {
        while (true) {
            if (lockA.tryLock()) { // Try to get the lock
                System.out.println("Thread 1: got lock A!");
                try {
                    Thread.sleep(1000);
                    System.out.println("Thread 1: Waiting for B...");
                    if (lockB.tryLock()) { // Try to get the lock
                        try {
                            System.out.println("Thread 1: acquired lock B!");
                        } finally {
                            lockB.unlock(); / / releases the lock
                            System.out.println("Thread 1: Release lock B.");
                            break; }}}catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    lockA.unlock(); / / releases the lock
                    System.out.println("Thread 1: Release lock A."); }}// Wait one second before continuing
            try {
                Thread.sleep(1000);
            } catch(InterruptedException e) { e.printStackTrace(); }}}}Copy the code

The execution results of the above procedures are as follows:As you can see from the above results, the above code also does not have deadlock problems.

conclusion

This article describes two ways to understand deadlock deadlock:

  • Sequential locking: By changing the order in which locks are acquired, the “loop request condition” is broken to avoid deadlocks.
  • Second type of polling lock: Resolve deadlocks by polling, which breaks the request and ownership condition. It is the implementation of the idea, to try to get by means of spin lock, in acquiring a lock on the way, if you have any a lock for failure, all previously acquired lock is released, wait for a period of time again after the previous process, thus avoiding a lock has been (a thread) takes up the embarrassing, so as to avoid the deadlock problem.

Reference & acknowledgements

Java Concurrent Programming

Concurrent original articles are recommended

  1. Thread 4 ways to create and use details!
  2. Is there a big difference between user threads and daemon threads in Java?
  3. Understand thread pools in depth
  4. 7 ways to create a thread pool, highly recommended…
  5. How great is pooling technology? I was shocked to see the comparison between threads and thread pools!
  6. Thread synchronization and locking in concurrency
  7. Synchronized “this” and “class”
  8. The difference between volatile and synchronized
  9. Is a lightweight lock faster than a weightweight lock?
  10. How can terminating the thread cause the service to go down?
  11. SimpleDateFormat 5 Solutions to Thread Insecurity!
  12. ThreadLocal not working well? That’s you!
  13. ThreadLocal memory overflow code
  14. Semaphore confessions: I’m the one with the restrictor!
  15. CountDownLatch: Wait till you latch on!
  16. CyclicBarrier: Once the driver has finished, he can start the train!
  17. Synchronized optimization means of lock expansion mechanism!
  18. Four optimizations in Synchronized. How many do you know?
  19. 4 pits in ReentrantLock!
  20. Why do unfair locks perform better?
  21. Deadlock detection tools for 4!

Follow “Java Chinese Community” for more interesting and informative Java concurrent articles.