Thread Deadlocks

A deadlock occurs when two or more threads are blocked forever because they are waiting for each other to release resources. In other words, each thread holds a resource and waits for a resource held by another thread, creating a circular dependency.

How Deadlocks Happen

Deadlocks typically happen when:

  1. Multiple threads hold locks on resources.
  2. Threads acquire locks in different orders.
  3. A thread holds a lock and waits for another thread to release a lock, while the other thread is waiting for the first one to release a lock.

Deadlock: Deadlock occurs when two or more threads have a circular dependency on a pair of synchronized objects. For example, Thread 1 holds Lock A and waits for Lock B, while Thread 2 holds Lock B and waits for Lock A.

Example Scenario:
  • Consider two resources Resource1 and Resource2 and two threads Thread1 and Thread2.
  • Thread1 locks Resource1 and then tries to lock Resource2.
  • Thread2 locks Resource2 and then tries to lock Resource1.
  • If Thread1 locks Resource1 and waits for Resource2, and Thread2 locks Resource2 and waits for Resource1, a deadlock occurs.
  • This scenario is demonstrated in following program.
//ThreadDeadLockDemo.java
class Resource {
    private final Object lock = new Object();

    public void method1(Resource other) {
        synchronized (lock) {
            System.out.println(Thread.currentThread().getName() + ": locked resource 1");
            try { Thread.sleep(50); } catch (InterruptedException e) {}
            other.method2();
        }
    }

    public void method2() {
        synchronized (lock) {
            System.out.println(Thread.currentThread().getName() + ": locked resource 2");
        }
    }
}

public class ThreadDeadLockDemo {

    public static void main(String[] args) {
        Resource resource1 = new Resource();
        Resource resource2 = new Resource();

        Thread thread1 = new Thread(() -> {
            resource1.method1(resource2);
        }, "Thread 1");

        Thread thread2 = new Thread(() -> {
            resource2.method1(resource1);
        }, "Thread 2");

        thread1.start();
        thread2.start();
		System.out.println("Press Ctrl+c to stop operation");		
    }
}

/*
C:\>javac ThreadDeadLockDemo.java

C:\>java ThreadDeadLockDemo
Press Ctrl+c to stop operation
Thread 2: locked resource 1
Thread 1: locked resource 1
*/

Correct Solution for above program

// ThreadDeadLockSolution.java
class Resource {
    private final Object lock = new Object();

    public void method1(Resource other) {
        Resource first = this;
        Resource second = other;

        // Enforce a strict lock order to avoid deadlocks
        if (System.identityHashCode(first) > System.identityHashCode(second)) {
            first = other;
            second = this;
        }

        System.out.println(Thread.currentThread().getName() + ": Trying to acquire lock on resource 1");

        synchronized (first.lock) {
            System.out.println(Thread.currentThread().getName() + ": locked resource 1");

            try { Thread.sleep(50); } catch (InterruptedException e) {}

            System.out.println(Thread.currentThread().getName() + ": Trying to acquire lock on resource 2");

            synchronized (second.lock) {
                System.out.println(Thread.currentThread().getName() + ": locked resource 2");
                other.method2();
            }

            System.out.println(Thread.currentThread().getName() + ": released lock on resource 2");
        }

        System.out.println(Thread.currentThread().getName() + ": released lock on resource 1");
    }

    public void method2() {
        synchronized (lock) {
            System.out.println(Thread.currentThread().getName() + ": locked resource 2 (inside method2)");
            System.out.println(Thread.currentThread().getName() + ": released lock on resource 2 (inside method2)");
        }
    }
}

public class ThreadDeadLockSolution {
    public static void main(String[] args) {
        Resource resource1 = new Resource();
        Resource resource2 = new Resource();

        Thread thread1 = new Thread(() -> {
            resource1.method1(resource2);
        }, "Thread 1");

        Thread thread2 = new Thread(() -> {
            resource2.method1(resource1);
        }, "Thread 2");

        System.out.println("Attempting to start threads...");

        thread1.start();
        thread2.start();

        // Simulate Deadlock Detection Message (In real cases, it may need a watchdog timer)
        try {
            Thread.sleep(100);
            System.out.println("Potential deadlock situation detected!");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("Deadlock avoided due to lock ordering.");
        System.out.println("Press Ctrl+C to stop operation.");
    }
}

/*

C:\>javac ThreadDeadLockSolution.java
C:\>java ThreadDeadLockSolution
Attempting to start threads...
Thread 1: Trying to acquire lock on resource 1
Thread 2: Trying to acquire lock on resource 1
Thread 1: locked resource 1
Thread 1: Trying to acquire lock on resource 2
Thread 1: locked resource 2
Thread 1: locked resource 2 (inside method2)
Thread 1: released lock on resource 2 (inside method2)
Thread 1: released lock on resource 2
Thread 1: released lock on resource 1
Thread 2: locked resource 1
Potential deadlock situation detected!
Thread 2: Trying to acquire lock on resource 2
Deadlock avoided due to lock ordering.
Press Ctrl+C to stop operation.
Thread 2: locked resource 2
Thread 2: locked resource 2 (inside method2)
Thread 2: released lock on resource 2 (inside method2)
Thread 2: released lock on resource 2
Thread 2: released lock on resource 1

*/

Deadlocks can severely impact performance and cause threads to be stuck indefinitely. It’s crucial to design thread synchronization carefully, maintain a consistent lock order, and monitor for potential deadlocks to ensure smooth multi-threaded execution.

Scroll to Top