ExerciseChallenge
Challenge
Five hangs, only three are deadlocks
25 minintermediate1–15 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 StarterHints
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.
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.
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?
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