Warm-up

Deadlock two threads and detect it

10 minjunior015 yrs

One concept, guided. Near-impossible to fail.

What this teaches

  • A deadlock is a cycle in the waits-for graph, and the JVM can prove one exists
  • Forcing the interleaving with a latch is what makes the bug reproducible
  • A global lock order removes the possibility rather than recovering from it
  • A thread blocked entering a monitor cannot be interrupted, timed out or rescued

Starter

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

/**
 * Warm-up: cause a deadlock on purpose, then let the JVM prove it.
 *
 * Two rules make this safe to run repeatedly:
 *
 *   - every thread you start here must be a daemon, so a permanently stuck one
 *     cannot stop the JVM exiting;
 *   - never join without a timeout.
 *
 * Both are habits worth keeping when writing any concurrency test.
 */
public class Starter {

    record Account(String id) {}

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

    public static void main(String[] args) throws Exception {
        var a = new Account("account-A");
        var b = new Account("account-B");

        // TODO 1: start two daemon threads. One locks `a` then `b`; the other
        // locks `b` then `a`. Run it a few times. Does it hang every time?

        // TODO 2: make it hang EVERY time. Use a CountDownLatch(2): inside the
        // first synchronized block, countDown() and then await(). Now neither
        // thread can reach for its second lock until both hold their first.
        //
        // Say in one sentence why the latch does not change whether the bug
        // exists — only whether you see it.

        // TODO 3: ask the JVM to confirm it, from the main thread:
        //
        //   ThreadMXBean mx = ManagementFactory.getThreadMXBean();
        //   long[] stuck = mx.findDeadlockedThreads();       // null if none
        //   for (ThreadInfo t : mx.getThreadInfo(stuck)) { ... }
        //
        // Print each thread's name, the lock it waits for, and the lock's
        // owner. Poll in a short loop rather than sleeping a fixed time.

        // TODO 4: the lock name prints as ClassName@hashcode, which is not
        // readable. Build a Map from that string to your own name:
        //
        //   o.getClass().getName() + "@" + Integer.toHexString(System.identityHashCode(o))
        //
        // and translate the output.

        // TODO 5: now fix it. Before locking, order the two accounts by id and
        // always take the lower one first. Use FRESH Account objects — the
        // threads from TODO 2 still hold the old ones and always will.
        // Confirm both transfers complete.

        // TODO 6: count how many lines that fix took, and say what it did NOT
        // require: no timeout, no retry, no detection, no coordination.

        // TODO 7: try to rescue a deadlocked thread. Start a thread blocked on
        // a synchronized block that will never be released, wait until its
        // state is BLOCKED, then interrupt() it. Print getState() and
        // isInterrupted() afterwards. Explain the combination you see.

        // TODO 8: repeat TODO 7 with a ReentrantLock and lockInterruptibly().
        // What is different, and what does that tell you about when the extra
        // ceremony of ReentrantLock is worth paying for?
    }
}

Run it locally:

cd exercises/java/concurrency/deadlock/01-warmup
javac Starter.java -d /tmp/out && java -cp /tmp/out Starter

Done when

  • You deadlocked two threads deliberately and it happened on every run
  • findDeadlockedThreads named both threads and both locks
  • Sorting the locks by a stable key made the same code complete
  • You interrupted a thread blocked on synchronized and showed it stayed BLOCKED

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