Concept of deadlock in Java

5

I came across the term deadlock in Java. But I can not understand it. So I would like to ask these questions:

  • What is a deadlock?
  • Why and how to avoid a deadlock?
asked by anonymous 16.11.2018 / 21:54

1 answer

6

Deadlock is the cyclic dependency that happens between two or more threads sharing two or more resources (variable, block of code, etc.). In other words, a thread T1 depends on another T2 , this T2 depends in turn on T1 .

This type of dependency happens exclusively due to the fault in the logic implemented by the programmer, which, when developing a multithreaded application, needs to worry not only with deadlock , but with others concepts related to competing programming, such as starvation , livelock and race condition , the latter being probably the most common to happen.

An example of code where deadlock occurs:

public class TestThread {
   public static Object Lock1 = new Object();
   public static Object Lock2 = new Object();

   public static void main(String args[]) {
      ThreadDemo1 T1 = new ThreadDemo1();
      ThreadDemo2 T2 = new ThreadDemo2();
      T1.start();
      T2.start();
   }

   private static class ThreadDemo1 extends Thread {
      public void run() {
         synchronized (Lock1) {
            System.out.println("Thread 1: Holding lock 1...");

            try { Thread.sleep(10); }
            catch (InterruptedException e) {}
            System.out.println("Thread 1: Waiting for lock 2...");

            synchronized (Lock2) {
               System.out.println("Thread 1: Holding lock 1 & 2...");
            }
         }
      }
   }
   private static class ThreadDemo2 extends Thread {
      public void run() {
         synchronized (Lock2) {
            System.out.println("Thread 2: Holding lock 2...");

            try { Thread.sleep(10); }
            catch (InterruptedException e) {}
            System.out.println("Thread 2: Waiting for lock 1...");

            synchronized (Lock1) {
               System.out.println("Thread 2: Holding lock 1 & 2...");
            }
         }
      }
   } 
}

When the application is run and both threads started, T1 gets lock (as if it were an access exclusivity), represented by the word synchronized , on the object Lock1 . That is, until T1 releases the lock on this object (that is, until the code block inside synchronized is completely executed), no other thread > you can access it. It can be seen that within this first block synchronized , there is another attempt of lock (represented by the other block synchronized ), this time about Lock2 . This means that the original lock on the object Lock1 will only be released if the thread T1 also gets the lock over Lock2 and run the code inside that second block. In short, at this point of code T1 has lock on Lock1 and PRECISE lock on Lock2 to finish its routine and free all the resources for other threads .

On the other hand, T2 , when started, acquires lock on Lock2 , and to finish its execution and release the in> lock on Lock1 .

With this scenario, it's easy to see what's happening: thread%>% has locked the object T1 and needs to win lock on Lock1 to Lock2 has already locked T2 and to finish you need to win lock on Lock2 , which in turn is locked by Lock1 . Here is the cyclic dependency, or deadlock . The output of this code when running:

Thread 1: Holding lock 1...
Thread 2: Holding lock 2...
Thread 1: Waiting for lock 2...
Thread 2: Waiting for lock 1...

At this point, your application is in a permanent state of waiting and will remain forever and that is why you never want a T1 .

And here comes the question: how to fix the code so that it does not occur? Simply reverse the order of the objects you want the lock . In other words, both threads will try to first lock the object deadlock (and get, also lock Lock1 ), execute the code and finish, / em> get the lock and also execute its due code:

public class TestThread {
   public static Object Lock1 = new Object();
   public static Object Lock2 = new Object();

   public static void main(String args[]) {
      ThreadDemo1 T1 = new ThreadDemo1();
      ThreadDemo2 T2 = new ThreadDemo2();
      T1.start();
      T2.start();
   }

   private static class ThreadDemo1 extends Thread {
      public void run() {
         synchronized (Lock1) {
            System.out.println("Thread 1: Holding lock 1...");

            try {
               Thread.sleep(10);
            } catch (InterruptedException e) {}
            System.out.println("Thread 1: Waiting for lock 2...");

            synchronized (Lock2) {
               System.out.println("Thread 1: Holding lock 1 & 2...");
            }
         }
      }
   }
   private static class ThreadDemo2 extends Thread {
      public void run() {
         synchronized (Lock1) {
            System.out.println("Thread 2: Holding lock 1...");

            try {
               Thread.sleep(10);
            } catch (InterruptedException e) {}
            System.out.println("Thread 2: Waiting for lock 2...");

            synchronized (Lock2) {
               System.out.println("Thread 2: Holding lock 1 & 2...");
            }
         }
      }
   } 
}

Running, here's the output :

Thread 1: Holding lock 1...
Thread 1: Waiting for lock 2...
Thread 1: Holding lock 1 & 2...
Thread 2: Holding lock 1...
Thread 2: Waiting for lock 2...
Thread 2: Holding lock 1 & 2...

And your application will be ready to continue to do whatever you want to do next.

    
17.11.2018 / 18:02