What Is a Deadlock in Java and How Can You Prevent It?

Deadlock in Java is a condition where two or more threads are blocked forever, each waiting for the other to release a lock. This occurs primarily in a multithreaded environment when synchronized resources are used in such a way that a circular wait condition arises. Understanding and preventing deadlocks is crucial for building efficient and bug-free Java applications.


🔁 What Causes Deadlock in Java?

Deadlock occurs when the following four conditions are met simultaneously:

  1. Mutual Exclusion: At least one resource must be held in a non-shareable mode.
  2. Hold and Wait: A thread holding at least one resource is waiting to acquire additional resources held by other threads.
  3. No Preemption: Resources cannot be forcibly removed from threads holding them.
  4. Circular Wait: A set of threads are each waiting for a resource that the next thread in the cycle holds.

Note: All four conditions must be true for a deadlock to occur. Removing any one can prevent deadlock.


🧵 Java Deadlock Code Example

Let’s look at a simple code example that demonstrates a classic deadlock scenario:


class SharedResource {
    void methodA() {
        synchronized (String.class) {
            System.out.println(Thread.currentThread().getName() + " locked String.class");

            synchronized (Integer.class) {
                System.out.println(Thread.currentThread().getName() + " locked Integer.class");
            }
        }
    }

    void methodB() {
        synchronized (Integer.class) {
            System.out.println(Thread.currentThread().getName() + " locked Integer.class");

            synchronized (String.class) {
                System.out.println(Thread.currentThread().getName() + " locked String.class");
            }
        }
    }
}

public class DeadlockExample {
    public static void main(String[] args) {
        SharedResource resource = new SharedResource();

        Thread t1 = new Thread(() -> resource.methodA(), "Thread-1");
        Thread t2 = new Thread(() -> resource.methodB(), "Thread-2");

        t1.start();
        t2.start();
    }
}

In the code above:

  • Thread-1 locks String.class and waits for Integer.class.
  • Thread-2 locks Integer.class and waits for String.class.

This creates a circular wait and results in a deadlock.


⚠️ How to Detect Deadlocks in Java?

Deadlocks can be hard to detect manually. Java provides several tools:

  • jconsole – A Java monitoring tool.
  • jstack – Prints Java thread stack traces for a Java process.
  • VisualVM – Graphical interface to monitor JVM.

Using jstack:


jstack <pid> | grep -A 30 "Found one Java-level deadlock"

This command identifies deadlocked threads and shows the locks they are holding or waiting for.


💡 How to Prevent Deadlocks in Java?

Several techniques can help prevent deadlocks in Java:

1. Lock Ordering

Always acquire locks in a consistent global order.


synchronized (String.class) {
    synchronized (Integer.class) {
        // do something
    }
}

Ensure that all threads follow this same lock order.

2. Try-Lock with Timeout

Use ReentrantLock.tryLock() with timeout to avoid indefinite waiting.


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

public class TryLockExample {
    private static ReentrantLock lock1 = new ReentrantLock();
    private static ReentrantLock lock2 = new ReentrantLock();

    public static void main(String[] args) {
        Thread t1 = new Thread(() -> {
            try {
                if (lock1.tryLock(1, TimeUnit.SECONDS)) {
                    Thread.sleep(500);
                    if (lock2.tryLock(1, TimeUnit.SECONDS)) {
                        System.out.println("Thread 1 acquired both locks");
                        lock2.unlock();
                    }
                    lock1.unlock();
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });

        Thread t2 = new Thread(() -> {
            try {
                if (lock2.tryLock(1, TimeUnit.SECONDS)) {
                    Thread.sleep(500);
                    if (lock1.tryLock(1, TimeUnit.SECONDS)) {
                        System.out.println("Thread 2 acquired both locks");
                        lock1.unlock();
                    }
                    lock2.unlock();
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });

        t1.start();
        t2.start();
    }
}

This technique prevents deadlock by avoiding waiting forever for a lock.

3. Avoid Nested Locks

Minimize the use of nested synchronized blocks. If not necessary, don’t acquire multiple locks simultaneously.

4. Use Higher-Level Concurrency Utilities

Prefer classes from java.util.concurrent like Semaphore, ExecutorService, etc., which provide better control and abstraction.


📊 Real-World Scenario Example

Let’s simulate a banking scenario where deadlock may occur if accounts try to transfer money simultaneously:


class Account {
    private int balance = 1000;

    public void withdraw(int amount) {
        balance -= amount;
    }

    public void deposit(int amount) {
        balance += amount;
    }

    public int getBalance() {
        return balance;
    }
}

class Transfer {
    public void transferMoney(Account from, Account to, int amount) {
        synchronized (from) {
            synchronized (to) {
                from.withdraw(amount);
                to.deposit(amount);
            }
        }
    }
}

public class BankDeadlock {
    public static void main(String[] args) {
        Account acc1 = new Account();
        Account acc2 = new Account();

        Transfer transfer = new Transfer();

        Thread t1 = new Thread(() -> transfer.transferMoney(acc1, acc2, 100));
        Thread t2 = new Thread(() -> transfer.transferMoney(acc2, acc1, 200));

        t1.start();
        t2.start();
    }
}

This program might deadlock because t

Please follow and like us:

Leave a Comment