Challenge

Five hangs, only three are deadlocks

25 minintermediate115 yrs

Edge cases. You have to reason, and two valid fixes differ.

What this teaches

  • findDeadlockedThreads proves a cycle, and its silence does not mean the service is healthy
  • A lock held across a slow call stalls a service without forming a cycle
  • A livelock has threads running and making no progress, so it looks nothing like a deadlock in metrics
  • Each of the five has a different fix, and naming the failure is what selects it

Starter

Starter.java
import java.lang.management.*;
import java.util.*;
import java.util.concurrent.*;
import java.util.concurrent.locks.ReentrantLock;

/**
 * Challenge: five services that stop making progress. Three are deadlocks.
 *
 * They look identical from outside — requests stop completing. They need
 * different fixes, and the JVM's detector only recognises three of them, which
 * is the point of the exercise.
 *
 * Every thread you start must be a daemon. Never join without a timeout.
 */
public class Starter {

    static Thread daemon(String name, Runnable body) {
        var t = new Thread(body, name);
        t.setDaemon(true);
        t.start();
        return t;
    }

    // ── 1 ─────────────────────────────────────────────────────────────────
    // Two threads, two monitors, opposite order.
    static void caseOne() { /* TODO: build it */ }

    // ── 2 ─────────────────────────────────────────────────────────────────
    // Two threads, two ReentrantLocks, opposite order.
    // Predict whether the detector sees this one before you run it.
    static void caseTwo() { /* TODO */ }

    // ── 3 ─────────────────────────────────────────────────────────────────
    // One lock. The holder is doing something slow — model a 30-second remote
    // call with a latch that is never counted down. Twenty threads want it.
    static void caseThree() { /* TODO */ }

    // ── 4 ─────────────────────────────────────────────────────────────────
    // Two threads, each politely releasing its lock and retrying immediately
    // when it cannot get the second. Neither ever wins.
    static void caseFour() { /* TODO */ }

    // ── 5 ─────────────────────────────────────────────────────────────────
    // A fixed thread pool of two. Each task submits another task to the SAME
    // pool and waits for its result.
    static void caseFive() { /* TODO */ }

    public static void main(String[] args) throws Exception {

        // TODO 1: implement all five. Keep each in its own method with its own
        // fresh locks — a stuck thread from an earlier case holds its locks
        // forever and will contaminate later ones.

        // TODO 2: write a helper that runs one case, waits a couple of
        // seconds, then reports:
        //   - what findDeadlockedThreads() returned
        //   - the state of each thread you started (BLOCKED / WAITING / RUNNABLE)
        //   - whether any work completed
        // Run it over all five and print a table.

        // TODO 3: classify each case: deadlock, lock held too long, livelock,
        // or none of those. Two of the five are not deadlocks.

        // TODO 4: three cases produce a cycle the JVM reports. Two do not, and
        // both of those still stop the service completely. Write the sentence
        // you would put in a runbook about what a green deadlock check means.

        // TODO 5: for case 4, look at CPU while it runs. How does it differ
        // from the others, and which metric would catch it?

        // TODO 6: give each case a fix, and make sure you have not written the
        // same fix five times. Ordering does not help case 3. A timeout does
        // not help case 5.

        // TODO 7: case 5 is the one most likely to reach production, because
        // it needs no lock at all. Say what property of the pool causes it and
        // what you would change.
    }
}

Run it locally:

cd exercises/java/concurrency/deadlock/02-challenge
javac Starter.java -d /tmp/out && java -cp /tmp/out Starter

Hints

  1. Hint 1

    Run every case under findDeadlockedThreads first and split them into 'reports a cycle' and 'does not'. That split is most of the answer.

  2. Hint 2

    One case has many threads BLOCKED on a single monitor and no cycle at all. Ask what the lock holder is waiting for.

  3. Hint 3

    One case has threads in RUNNABLE the whole time and completes nothing. CPU is high, not flat — which detector would ever catch that?

  4. Hint 4

    One is not a locking bug at all. Look at what the pool threads are waiting for, and who would have to finish for them to proceed.

Done when

  • Each of the five is classified as deadlock, lock-held-too-long, livelock, or neither
  • You ran the JVM detector against all five and recorded what it said
  • Each case has a named fix, and the three deadlocks do not all get the same one
  • You can say what each case looks like in CPU and completion-rate metrics
  • You explained why a green findDeadlockedThreads is not evidence of health

← Back to What causes a deadlock and how do you prevent it?