Java Multithreading

Deadlock in Java Multi-Threading

Clear deadlock concept with an example.

Vikram Gupta
4 min readDec 31, 2022

This article is all about Deadlock, we’ll see a deadlock program and will find an approach to overcome the deadlock condition.

What Is a Deadlock?

Deadlock occurs when multiple threads need the same locks but obtain these locks in a different order. A Java multithreaded program may suffer from the deadlock condition because the synchronized keyword causes the executing thread to block while waiting for the lock associated with the specified object.

Deadlock is a condition where the threads are waiting infinitely for the resources(locks).

Now Let’s Write a Program and See What’s the Issue With This Program.

There are two threads in the program and two locks are created. Each thread needs both locks to execute the critical section. Hence both threads are synchronized on these two locks but in a different order.

  1. The thread-1 first acquires lock1 and then lock2 to execute completely.
  2. But thread-2 first acquires lock2 and then lock1 to execute completely.

Now let’s go through the program and find out why the deadlock can occur.

public class Main {
public static Object lock1 = new Object();
public static Object lock2 = new Object();

public static void main(String[] args) {
new Thread1().start();
new Thread2().start();
}

private static class Thread1 extends Thread {
@Override
public void run() {
synchronized (lock1) {
System.out.println("Thread-1 acquired lock1");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
System.out.println("Thread-1 interrupted.");
}
System.out.println("Thread-1 waiting for lock2");
synchronized (lock2) {
System.out.println("Thread-1 acquired lock2");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
System.out.println("Thread-1 interrupted.");
}
}
System.out.println("Thread-1 releases lock2");
}
System.out.println("Thread-1 releases lock1");
}
}

private static class Thread2 extends Thread {
@Override
public void run() {
synchronized (lock2) {
System.out.println("Thread-2 acquired lock2");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
System.out.println("Thread-2 interrupted.");
}
System.out.println("Thread-2 waiting for lock1");
synchronized (lock1) {
System.out.println("Thread-2 acquired lock1");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
System.out.println("Thread-2 interrupted.");
}
}
System.out.println("Thread-2 releases lock1");
}
System.out.println("Thread-2 releases lock2");
}
}
}

Output:

Thread-1 acquired lock1
Thread-2 acquired lock2
Thread-2 waiting for lock1
Thread-1 waiting for lock2
//Both threads are waiting infinitely for the locks.

We can see that Thread-1 acquired lock1 and Thread-2 acquired lock2. Thread-2 is waiting for lock1 to execute its second synchronized block. Similarly, Thread-1 wants lock2 to execute its second synchronized block but lock2 is not available to acquire it as it is already acquired by Thread-2. Hence these threads are waiting infinitely for the locks to execute completely.

Solution for Deadlock:

The deadlock occurred as the two threads were acquiring the locks in a different order. If we synchronize the thread in the same order then we can get rid of deadlock.

public class Main {
public static Object lock1 = new Object();
public static Object lock2 = new Object();

public static void main(String[] args) {
new Thread1().start();
new Thread2().start();
}

private static class Thread1 extends Thread {
@Override
public void run() {
synchronized (lock1) {
System.out.println("Thread-1 acquired lock1");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
System.out.println("Thread-1 interrupted.");
}
System.out.println("Thread-1 waiting for lock2");
synchronized (lock2) {
System.out.println("Thread-1 acquired lock2");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
System.out.println("Thread-1 interrupted.");
}
}
System.out.println("Thread-1 releases lock2");
}
System.out.println("Thread-1 releases lock1");
}
}

private static class Thread2 extends Thread {
@Override
public void run() {
synchronized (lock1) {
System.out.println("Thread-2 acquired lock1");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
System.out.println("Thread-2 interrupted.");
}
System.out.println("Thread-2 waiting for lock2");
synchronized (lock2){
System.out.println("Thread-2 acquired lock2");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
System.out.println("Thread-2 interrupted.");
}
}
System.out.println("Thread-2 releases lock2");
}
System.out.println("Thread-2 releases lock1");
}
}
}

Output:

Thread-1 acquired lock1
Thread-1 waiting for lock2
Thread-1 acquired lock2
Thread-1 releases lock2
Thread-1 releases lock1
Thread-2 acquired lock1
Thread-2 waiting for lock2
Thread-2 acquired lock2
Thread-2 releases lock2
Thread-2 releases lock1

Thread-1 acquired the lock1, and hence Thread-2 is waiting for the lock1. Then Thread-1 acquired the lock2 and executed both synchronized blocks. Finally, it releases both locks one after the other.

After the execution of Thread-1, Thread-2 acquired lock1 and lock2 and executed both synchronized blocks.

Similarly, Thread-2 can execute its synchronized blocks and then Thread-1 can execute.

That’s all for this article. Hope you have enjoyed this article.

You can follow me here.

--

--

Vikram Gupta

-: Empowering Developers to Ace Their Technical Interviews :-