Warm-up

Name the edge

10 minintermediate210 yrs

One concept, guided. Near-impossible to fail.

What this teaches

  • happens-before is a guarantee about visibility, not about clock order
  • Thread.start() and Thread.join() are edges, so plain fields cross them
  • Transitivity means one edge carries everything written before it
  • Where you cannot name an edge, there is no guarantee at all

Starter

Starter.java
import java.util.*;
import java.util.concurrent.*;

/**
 * Warm-up: for each snippet, name the edge — or say there isn't one.
 *
 * This is the whole skill. Not "is this thread-safe?" but "which specific
 * happens-before edge carries this write to that read?". If you can name it,
 * the code is correct. If you cannot, no amount of running it will tell you.
 */
public class Starter {

    static class Payload {
        int size;
        String name;
        String result;
    }

    /* ─────────── 1 ─────────── */

    static void beforeStart() throws Exception {
        Payload p = new Payload();
        p.size = 42;                       // plain field
        p.name = "invoice";                // plain field

        Thread t = new Thread(() -> System.out.println("  1: " + p.name + "/" + p.size));
        t.start();
        t.join();
        // TODO: which edge makes the two writes visible inside the thread?
    }

    /* ─────────── 2 ─────────── */

    static void afterJoin() throws Exception {
        Payload p = new Payload();
        Thread t = new Thread(() -> p.result = "done");   // plain field
        t.start();
        t.join();
        System.out.println("  2: " + p.result);
        // TODO: which edge makes the worker's write visible to this thread?
    }

    /* ─────────── 3 ─────────── */

    static void throughALatch() throws Exception {
        Payload p = new Payload();
        CountDownLatch ready = new CountDownLatch(1);

        Thread producer = new Thread(() -> {
            p.size = 7;                    // plain
            p.name = "report";             // plain
            ready.countDown();
        });
        Thread consumer = new Thread(() -> {
            try {
                ready.await();
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
                return;
            }
            System.out.println("  3: " + p.name + "/" + p.size);
        });
        producer.start();
        consumer.start();
        producer.join();
        consumer.join();
        // TODO: name THREE edges chained together here. The latch is only one
        // of them, and it does not know these fields exist.
    }

    /* ─────────── 4 ─────────── */

    static volatile boolean unused = false;

    static void noEdge() throws Exception {
        Payload p = new Payload();

        Thread writer = new Thread(() -> p.name = "late");
        Thread reader = new Thread(() -> {
            // No edge with `writer` at all. Whatever this prints proves nothing.
            System.out.println("  4: " + p.name);
        });
        writer.start();
        reader.start();
        writer.join();
        reader.join();
        // TODO: this one has NO edge between the write and the read. Say what
        // the JVM is permitted to do, and why the output you get is not
        // evidence either way.
    }

    public static void main(String[] args) throws Exception {
        beforeStart();
        afterJoin();
        throughALatch();
        noEdge();

        // TODO 5: three of the four are guaranteed. Add `volatile` to every
        // field in Payload and confirm nothing changes about the guarantees.
        // Then remove it again and say what the annotation would have been
        // communicating to the next reader.

        // TODO 6: in snippet 4, insert Thread.sleep(100) before the read so
        // it "works". Explain in one sentence why the code is no more correct
        // than it was.

        // TODO 7: fix snippet 4 properly. There are at least three ways —
        // pick the cheapest and say why the others are heavier than needed.
    }
}

Run it locally:

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

Done when

  • You named the edge for each of the four snippets before running anything
  • You removed a volatile that an existing edge already made redundant
  • You found the one snippet with no edge and can say what may happen
  • You can state why adding a sleep is not a fix

← Back to What is the happens-before relationship?