Back to Deadlock, Livelock & Starvation
Q2Question 2 of 4

How do you detect and prevent deadlocks?

Key Concepts
  • jstack prints all thread states and automatically identifies deadlocks with the message 'Found one Java-level deadlock'.
  • ThreadMXBean.findDeadlockedThreads() returns deadlocked thread IDs programmatically for automated monitoring.
  • tryLock(timeout) prevents indefinite waiting: the thread gives up and retries instead of blocking forever.
  • ReadWriteLock reduces contention by allowing multiple concurrent readers while keeping writers exclusive.
  • Lock ordering is the primary prevention strategy; tryLock and ReadWriteLock are secondary defenses.
  • In production, combine lock ordering (prevention), tryLock (safety net), and deadlock monitoring (alerting).

Definition

Detect deadlocks with jstack and ThreadMXBean, prevent them with lock ordering, tryLock(timeout), and ReadWriteLock.
  • Detection via jstack – jstack dumps all thread states and stack traces. The JVM automatically detects deadlocks and prints "Found one Java-level deadlock" with the involved threads and locks.
  • Programmatic detection – ThreadMXBean.findDeadlockedThreads() returns an array of deadlocked thread IDs, or null if none. Schedule this in a health-check thread.
  • tryLock prevention – ReentrantLock.tryLock(timeout, unit) returns false if the lock cannot be acquired within the timeout, allowing the thread to back off instead of blocking forever.
  • ReadWriteLock – ReentrantReadWriteLock allows multiple readers concurrently (read lock is shared) while writers take exclusive access. This reduces the contention that leads to deadlock.
  • Lock ordering – the primary prevention strategy: always acquire locks in a deterministic order to break circular wait.

Detection is like the bank manager doing a roll call: "Who is standing where, holding what key, waiting for what?" The manager finds the circular dependency. Prevention is a combination: the bank rule says always enter the lower-numbered vault first (lock ordering). If you cannot get a key within 5 minutes, step back and try later (tryLock). For reading the register, multiple tellers can read simultaneously (ReadWriteLock).

Understanding the Concepts

How do you detect a deadlock with jstack?

jstack is a command-line tool that comes with the JDK. It dumps the state and stack trace of every thread in the target JVM.

BASH
# Find the process ID
jps
# Take a thread dump
jstack 12345

When a deadlock exists, jstack prints a special section at the end.

TEXT
Found one Java-level deadlock:
=============================
"TransferThread-1":
  waiting to lock monitor 0x00007f... (a com.bank.model.Account)
  which is held by "TransferThread-2"
"TransferThread-2":
  waiting to lock monitor 0x00007f... (a com.bank.model.Account)
  which is held by "TransferThread-1"

Java stack information for the threads listed above:
"TransferThread-1":
  at com.bank.service.TransferService.transfer(TransferService.java:25)
  - waiting to lock <0x000000076ab...> (a com.bank.model.Account)
  - locked <0x000000076ac...> (a com.bank.model.Account)

How do you detect deadlocks programmatically?

Schedule a health check that calls ThreadMXBean.findDeadlockedThreads() every 30 seconds.

JAVA
package com.bank.monitoring;

import java.lang.management.ManagementFactory;
import java.lang.management.ThreadInfo;
import java.lang.management.ThreadMXBean;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

public class DeadlockMonitor {
    public static void startMonitoring() {
        Executors.newSingleThreadScheduledExecutor().scheduleAtFixedRate(() -> {
            ThreadMXBean tmx = ManagementFactory.getThreadMXBean();
            long[] ids = tmx.findDeadlockedThreads();
            if (ids != null) {
                ThreadInfo[] infos = tmx.getThreadInfo(ids, true, true);
                StringBuilder sb = new StringBuilder("DEADLOCK DETECTED:\n");
                for (ThreadInfo info : infos) {
                    sb.append("  ").append(info.getThreadName())
                      .append(" waiting for ").append(info.getLockName())
                      .append(" held by ").append(info.getLockOwnerName())
                      .append("\n");
                }
                System.err.println(sb);
                // In production: send to PagerDuty or alerting system
            }
        }, 0, 30, TimeUnit.SECONDS);
    }
}

How does tryLock prevent deadlock?

tryLock(timeout) returns false if the lock is not available within the timeout, allowing the thread to release any locks it holds and retry.

JAVA
package com.bank.service;

import java.math.BigDecimal;
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;

public class SafeTransferService {
    public boolean transfer(ReentrantLock fromLock, ReentrantLock toLock,
                            BigDecimal amount) throws InterruptedException {
        for (int attempt = 0; attempt < 3; attempt++) {
            boolean gotFrom = false;
            boolean gotTo = false;
            try {
                gotFrom = fromLock.tryLock(100, TimeUnit.MILLISECONDS);
                gotTo = toLock.tryLock(100, TimeUnit.MILLISECONDS);
                if (gotFrom && gotTo) {
                    // perform transfer
                    return true;
                }
            } finally {
                if (gotTo) toLock.unlock();
                if (gotFrom) fromLock.unlock();
            }
            // Random backoff to reduce livelock
            Thread.sleep(ThreadLocalRandom.current().nextInt(10, 50));
        }
        return false; // failed after 3 attempts
    }
}

How does ReadWriteLock reduce contention?

In banking, most account operations are reads (balance inquiry, statement view). ReentrantReadWriteLock allows multiple readers concurrently.

JAVA
package com.bank.service;

import java.math.BigDecimal;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

public class AccountWithRWLock {
    private final ReadWriteLock rwLock = new ReentrantReadWriteLock();
    private BigDecimal balance;

    public AccountWithRWLock(BigDecimal initialBalance) {
        this.balance = initialBalance;
    }

    public BigDecimal getBalance() {
        rwLock.readLock().lock();
        try { return balance; }
        finally { rwLock.readLock().unlock(); }
    }

    public void debit(BigDecimal amount) {
        rwLock.writeLock().lock();
        try { balance = balance.subtract(amount); }
        finally { rwLock.writeLock().unlock(); }
    }
}
// 100 threads reading balance: ALL concurrent (shared read lock)
// 1 thread debiting: exclusive (write lock blocks readers and other writers)

What is a livelock?

A livelock is similar to a deadlock, but the threads are actively running instead of blocked. They keep retrying without making progress.

TEXT
Thread-1: tryLock(A) success, tryLock(B) fail, release A, retry
Thread-2: tryLock(B) success, tryLock(A) fail, release B, retry
Both keep retrying forever: running but making no progress.
Fix: add random backoff (Thread.sleep(random)) between retries.

What should a senior engineer know?

  • Defense in depth: use lock ordering as primary prevention, tryLock as a safety net, and deadlock monitoring as a last-line detection.
  • Thread dump analysis tools: tools like fastThread.io and TDA (Thread Dump Analyzer) parse jstack output and visualize deadlock chains.
  • Database deadlocks: databases detect deadlocks and kill one transaction as the victim. Java-level deadlocks have no such automatic resolution.

Visual Overview

DIAGRAM
  Deadlock Detection and Prevention Stack:

  ┌───────────────────────────────────────────┐
  │  LAYER 1: Prevention (lock ordering)       │  Primary
  │  Always acquire locks in sorted order      │
  ├───────────────────────────────────────────┤
  │  LAYER 2: Safety net (tryLock + timeout)   │  Secondary
  │  Give up after deadline, retry with backoff│
  ├───────────────────────────────────────────┤
  │  LAYER 3: Contention reduction (RWLock)    │  Optimization
  │  Multiple readers, exclusive writers       │
  ├───────────────────────────────────────────┤
  │  LAYER 4: Detection (jstack, ThreadMXBean) │  Alerting
  │  Scheduled health check every 30 seconds   │
  └───────────────────────────────────────────┘

Key Takeaway

Deadlock detection uses jstack which automatically prints "Found one Java-level deadlock" with the threads and locks involved. Programmatically, ThreadMXBean.findDeadlockedThreads() returns deadlocked thread IDs for automated monitoring. Prevention uses lock ordering (primary: break circular wait), tryLock with timeout (secondary: break hold-and-wait), ReadWriteLock (reduce contention by allowing concurrent reads), and lock-free structures (eliminate locks entirely).

In production, apply defense in depth: lock ordering prevents most deadlocks, tryLock catches edge cases, and deadlock monitoring alerts when prevention fails. ReadWriteLock is especially valuable in banking where 95% of account operations are reads.

Remember one sentence: detect deadlocks with jstack and ThreadMXBean, and prevent them with consistent lock ordering as the primary strategy and tryLock as the safety net.

---

Share
Use ← → arrow keys to navigate