Analyzing Deadlock Conditions in Java Multithreading

Understanding Deadlock

Deadlock represents a critical failure state in concurrent programming where two or more threads are blocked forever, each waiting for a resource held by the other. It is not a feature to be utilized, but a severe error condition that must be prevented during system design.

This phenomenon typically arises when nested synchronization blocks are utilized across multiple threads with conflicting resource acquisition orders. If Thread 1 holds Lock A and requests Lock B while Thread 2 holds Lock B and requests Lock A, the application enters a standstill.

Consider the following implementation which simulates a circular wait scenario using two distinct resources:

public class DeadlockDemo {

    // Define two shared resources acting as locks
    private static final Object resourceOne = new Object();
    private static final Object resourceTwo = new Object();

    public static void main(String[] args) {
        // Thread 1 attempts to lock resources in order: One -> Two
        Thread threadA = new Thread(() -> {
            synchronized (resourceOne) {
                System.out.println("Thread A: Acquired lock on Resource One. Attempting to lock Resource Two...");
                // Simulate processing to increase the likelihood of context switch
                try { Thread.sleep(50); } catch (InterruptedException e) { Thread.currentThread().interrupt(); }
                
                synchronized (resourceTwo) {
                    System.out.println("Thread A: Acquired lock on Resource Two. Action complete.");
                }
            }
        });

        // Thread 2 attempts to lock resources in reverse order: Two -> One
        Thread threadB = new Thread(() -> {
            synchronized (resourceTwo) {
                System.out.println("Thread B: Acquired lock on Resource Two. Attempting to lock Resource One...");
                // Simulate processing to increase the likelihood of context switch
                try { Thread.sleep(50); } catch (InterruptedException e) { Thread.currentThread().interrupt(); }
                
                synchronized (resourceOne) {
                    System.out.println("Thread B: Acquired lock on Resource One. Action complete.");
                }
            }
        });

        threadA.start();
        threadB.start();
    }
}

When this application executes, it often results in a permanent freeze. The execution flow proceeds as follows:

  1. Thread A successfully enters the first synchronized block, locking resourceOne.
  2. Thread B successfully enters its first synchronized block, locking resourceTwo.
  3. Thread A proceeds to the nested block and attempts to acquire resourceTwo. However, it is forced to wait because Thread B currently holds it.
  4. Thread B proceeds to its nested block and attempts to acquire resourceOne. It is forced to wait because Thread A currently holds it.

At this stage, Thread A cannot release resourceOne until it obtains resourceTwo, and Thread B cannot release resourceTwo until it obtains resourceOne. Both threads remain in a BLOCKED state indefinitely. To prevent this issue, developers should ensure that locks are always acquired in a consistent, global order across all threads.

Tags: java Concurrency multithreading deadlock Synchronization

Posted on Thu, 24 Sep 2026 16:27:46 +0000 by spikeon