# NOTES — seven files, nine measurements, and three of them are supposed to be wrong

## Run it first

```
..\..\..\..\.toolchain\jdk-21\bin\javac.exe -Xlint:all -d out *.java
..\..\..\..\.toolchain\jdk-21\bin\java.exe -cp out Main
```

`javac -Xlint:all` prints nothing and exits 0. Every class here compiles clean, which is the first
thing to notice: a lost update is not a compile error and there is no flag that finds one.

Real output, verbatim, from one run:

```
line length = 38 chars, crowd = 200 threads, 1000 writes each, expected total = 200000

--- 1. int written; written++
  expected 200000, got 90460  (lost 109540)

--- 2. volatile int written; written++
  expected 200000, got 138353  (lost 61647)

--- 3. AtomicInteger.incrementAndGet()
  expected 200000, got 200000  (lost 0)

--- 4. two fields, one invariant: bytes == lines * 38
  two atomics, one per field   inconsistent reads: 23905, final tally Tally[lines=200000, bytes=7600000] consistent: true
  one private lock over both   inconsistent reads: 0, final tally Tally[lines=200000, bytes=7600000] consistent: true

--- 5. ReentrantLock: tryLock, and re-entering a lock you hold
  tryWrite while another thread holds the lock : false
  tryWrite once it is free                     : true
  hold count seen inside write, called under lock: 2

--- 6. synchronized(this): an outside caller takes your lock
  write() finished while an outsider held the monitor: false (waited 500ms)
  after the outsider let go                          : true, lines=1

--- 7. ConcurrentHashMap: 200 threads, one message, 1000 records each
  get(k) then put(k, v+1)   count("retrying") = 26443
  merge(k, 1, Integer::sum) count("retrying") = 200000

--- 8. collapsing duplicates: 200 threads, the identical key, one delivery allowed
  getAndSet        accepted: 1
  get() then set() accepted: 2

--- 9. the one job volatile has: a stop flag
  boolean stopped          reader still spinning 2s after stop(): true
  volatile boolean stopped reader still spinning 2s after stop(): false
```

## Which of those numbers you can rely on, measured over sixteen runs

This matters more than any single run, because a demonstration of a race that only sometimes races
teaches the wrong thing.

| Line | Over 16 runs | Reliable? |
|---|---|---|
| block 1, plain `int` | 77175 to 196507, never 200000 | yes, always wrong |
| block 2, `volatile int` | 47113 to 177229, never 200000 | yes, always wrong |
| block 3, `AtomicInteger` | 200000, every run | yes, always right |
| block 2 versus block 1 | volatile lost more in 11 of 16 | **no** — direction is not stable |
| block 4, two atomics | 23905 to 1714878 inconsistent reads | yes, always non-zero |
| block 4, one lock | 0 inconsistent reads, every run | yes, always zero |
| block 6, blocked write | `false` then `true`, every run | yes |
| block 7, get-then-put | 7617 to 36064, best run kept 18% | yes, always wrong |
| block 7, `merge` | 200000, every run | yes, always right |
| block 8, `getAndSet` | exactly 1, every run | yes, and it is a guarantee |
| block 8, get-then-set | 1, 2, 3 and 7 all observed | **no** — it was correct in 1 of 16 |
| block 9, plain flag | still spinning, every run | yes |

Two rows say **no**, and they are worth more than the rows that say yes.

Block 2's direction is a hardware artefact, so do not learn "volatile is slower and worse". What is
stable is that the keyword changed nothing about correctness.

Block 8's get-then-set version returned the right answer on one run in sixteen. That is what a race
demonstration looks like when it is honest. Broken code passing once is the same phenomenon
`corpus/lru-cache/reference/DECISION_LOG.md` records at length. An earlier draft of that suite's
read-storm scenario passed against a known-broken cache every single time. It was replaced rather
than shipped.

---

## `Appender.java`

```java
void write(String formattedLine);
```

**One method, and the contract's sentence above it is the whole reason this lesson exists.** The
logger calls this for the same instance from many threads and never serialises the calls. So the
first question about any `Appender` with a field is what guards that field. `corpus/logger`'s
reference has no such field, which is why its own stress suite passes: `Logger.offer` builds a
fresh `String` from a local and touches nothing shared.

---

## `LineCounters.java`

### `Unguarded`

```java
private int written;

@Override
public void write(String formattedLine) {
    written++;
}
```

**Nothing here is guaranteed at all.** Two threads can read 41, both add one, and both store 42. It
compiles with `-Xlint:all` clean, passes any single-threaded test, and loses between 2% and 61% of
its increments under the corpus's crowd of 200.

**`written++` is three operations.** Load, add, store. That is the whole bug, and it is the reason
the next class does not fix it.

### `VolatileField`

```java
private volatile int written;
```

**What this line does guarantee.** Any thread reading `written` sees the most recent write to it,
and reads and writes of it cannot be reordered against the accesses around them. The read is never
stale and never hoisted out of a loop.

**What it does not guarantee, and this is the correction worth carrying out of this lesson.**
Atomicity of anything. `written++` is still load, add, store, with the same window between the
first and the last. Java's `volatile` is a stronger thing than C++'s, and still not `std::atomic`.

**What breaks without the keyword is not this.** Compare block 1 with block 2: removing `volatile`
from a counter changes the size of the loss and not its existence. The failure `volatile` actually
prevents is block 9's, and that failure is invisible here.

### `Atomic`

```java
private final AtomicInteger written = new AtomicInteger();

written.incrementAndGet();
```

**One operation, so there is no window.** On x86 this compiles to a `lock xadd`, the same
instruction `std::atomic<int>::fetch_add` gives you, with no monitor and no blocking. Block 3
returns 200000 on every run, and that is a guarantee rather than an observation.

**`final` on the field, and it is load-bearing.** The reference must never be replaced, or two
threads end up counting into different objects. `final` also means the field needs no `volatile`:
J4's page on safe publication is the rule, and this is the case it covers.

**What it cannot do is block 4.** An atomic makes one field's update indivisible. It says nothing
about two fields being updated together, which is the next class.

---

## `TailAppender.java`

### `Tally`, and why the two numbers are read as one value

```java
public record Tally(int lines, long bytes) {

    public boolean consistentFor(int lineLength) {
        return bytes == (long) lines * lineLength;
    }
}
```

**The invariant is written down as code, which is what makes block 4 measurable.** Every line in
the demo is 38 characters, so `bytes == lines * 38` has to hold for any tally that describes a real
moment. A tally that fails this describes a moment that never happened.

**The cast is on the left operand.** `(long)(lines * lineLength)` would multiply two `int`s,
overflow at 2.1 billion, and widen the wrong answer. Not reachable here; the habit costs nothing.

### `TwoAtomics` — correct per field, wrong across fields

```java
private final AtomicInteger lines = new AtomicInteger();
private final AtomicLong bytes = new AtomicLong();

public void write(String formattedLine) {
    lines.incrementAndGet();
    bytes.addAndGet(formattedLine.length());
}
```

**Both increments are atomic and no count is ever lost.** Block 4's final tally is
`Tally[lines=200000, bytes=7600000]`, consistent, on every run. Everything about this class is
correct once the writers have stopped.

**The bug is only visible to a reader arriving mid-flight.**

```java
public Tally tally() {
    return new Tally(lines.get(), bytes.get());
}
```

Two atomic reads with a gap between them. A writer landing in that gap gives a tally that counts a
line whose characters have not arrived. Measured: **23905 broken reads** on the run above, and
between 23905 and 1714878 across sixteen runs. Never zero.

**This is the shape that makes people distrust concurrency.** Every individual operation is
correct, the end state is correct, and the class is still wrong, because the invariant spans two
fields and no per-field mechanism can span anything. It is also why an interviewer's "why a lock
and not an atomic?" has a real answer: an atomic guards one field, a lock guards a rule.

### `Guarded` — the shape to reach for

```java
private final Object lock = new Object();
private int lines;
private long bytes;
```

**`private` means the lock cannot be named from outside, so nothing outside can take it.** Compare
`ExposedMonitor`, where the lock is `this` and block 6 shows a stranger holding it. **`final` means
the reference cannot be repointed**, so two threads can never end up synchronizing on different
objects while both believe they are guarding the same fields. Drop `final` and you get a lock that
compiles, runs, and protects nothing.

**Plain `int` and `long`, with no `volatile` and no atomic wrapper.** This is the part that looks
like an omission and is not. Releasing this monitor happens-before the next acquisition of it, so
the next thread in sees both writes. Adding `volatile` here would be noise, and it would suggest to
a reader that you were not sure which mechanism was doing the work.

**`long bytes` without `volatile` is safe only because of the lock.** A non-volatile `long` may be
read as two halves from two different writes, which is the one tearing hazard Java has. Every read
of this field is under the lock, so no unsynchronized read exists.

```java
public void write(String formattedLine) {
    synchronized (lock) {
        lines++;
        bytes += formattedLine.length();
    }
}
```

**Both mutations inside one block is the entire design.** Two blocks, one per field, would be
`TwoAtomics` with more syntax. The block is the unit of atomicity, and its boundaries are the
statement of what moves together.

**`formattedLine.length()` is called inside the block and could have been called outside it.**
Moving it out shortens the critical section by one call on an immutable `String`. Left in because
the block already reads at a glance; if this were a formatter call that could throw or block, out
is the correct answer.

```java
public Tally tally() {
    synchronized (lock) {
        return new Tally(lines, bytes);
    }
}
```

**The read is under the same lock, and that is what makes block 4 print 0.** A `tally()` without
the keyword would be exactly as broken as `TwoAtomics`, and would still return the right answer
after the writers stopped. This is `corpus/lru-cache`'s `get` in miniature. A read that participates
in an invariant is not a free operation. Leaving it unguarded is the single mistake that problem's
stress suite exists to catch.

**`synchronized` releases on the exception path too.** No `finally`, and nothing to forget. That is
`std::lock_guard`, not `std::mutex::lock`.

### `Explicit` — `std::mutex`'s shape, and the two things it can do that a monitor cannot

```java
private final ReentrantLock lock = new ReentrantLock();

public void write(String formattedLine) {
    lock.lock();
    try {
        lines++;
        bytes += formattedLine.length();
        holdCountSeenInsideWrite = lock.getHoldCount();
    } finally {
        lock.unlock();
    }
}
```

**The `finally` is not optional and nothing warns you.** Java has no destructor and no scope guard,
so an exception between `lock()` and `unlock()` leaves the lock held forever and every other thread
blocked. J9's `try`-with-resources does not apply: `ReentrantLock` is not an `AutoCloseable`. The
one-line rule is that `lock()` is immediately followed by `try`, always.

```java
public boolean tryWrite(String formattedLine) {
    if (!lock.tryLock()) {
        return false;
    }
```

**`std::mutex::try_lock`, and the reason to choose `ReentrantLock` over `synchronized` at all.**
There is no way to write this with the `synchronized` keyword; a monitor has no non-blocking
acquire. Block 5 prints `false` while another thread holds the lock and `true` once it is free,
with the hand-off coordinated by latches so the output is the same on every run.

The other capabilities in the same family, none of which a monitor has: `tryLock(timeout, unit)`,
`lockInterruptibly()`, `newCondition()` for more than one wait set, and a fair-ordering
constructor. Absent a reason from that list, `synchronized` is the smaller thing to read.

```java
public int writeWhileHolding(String formattedLine) {
    lock.lock();
    try {
        write(formattedLine);
```

**This is the line that would be undefined behaviour in C++.** `write` takes a lock the calling
method already holds. `std::mutex` gives you a hang or worse; `std::recursive_mutex` exists for
exactly this. In Java both lock kinds are reentrant and there is no non-reentrant option, so the
call returns normally and block 5 prints the hold count as **2**.

**Reentrancy is a hazard of a different kind, not the absence of one.** The critical section is now
as long as both methods together, and neither method's author chose that. The C++ habit of keeping
locking public methods and non-locking private helpers apart is still the better structure here.

```java
public void holdUntil(CountDownLatch held, CountDownLatch release) throws InterruptedException {
```

**Demo scaffolding, and it is here rather than in `Main` for one reason.** The lock is `private`,
so only this class can hold it while `Main` probes `tryWrite`. That is the same property that makes
a private lock worth having, seen from the inside.

---

## `ExposedMonitor.java`

```java
public synchronized void write(String formattedLine) {
    lines++;
}
```

**`synchronized` on a method is `synchronized (this)` around the whole body.** Two things follow,
and only the first is intended. The mutation is guarded. The lock guarding it is now reachable by
anyone holding a reference to the appender.

**What breaks is not visible in this file.** `Main` block 6 does this, from a completely different
class:

```java
synchronized (exposed) {
    // exposed.write() now blocks until this block ends
}
```

```
  write() finished while an outsider held the monitor: false (waited 500ms)
  after the outsider let go                          : true, lines=1
```

A destination that stops accepting lines for as long as some unrelated code holds a lock you did
not know you published. No exception, no warning, and nothing in this file to read that would tell
you. The fix is one field: `private final Object lock = new Object()`.

**When `synchronized (this)` is still the right answer.** `corpus/lru-cache/reference/src/LruCache.java`
uses it on every method, deliberately. It is defensible there for two reasons. The class is one unit
whose entire state moves under one lock, and no reference to it reaches code that could lock it. The
threshold, with the three triggers spelled out, is in `from-cpp.md`.

---

## `RepeatCounts.java`

### `GetThenPut`

```java
private final Map<String, Integer> counts = new ConcurrentHashMap<>();

public void record(String message) {
    Integer seen = counts.get(message);
    counts.put(message, seen == null ? 1 : seen + 1);
}
```

**The map is a `ConcurrentHashMap` and it is not the problem.** The `get` is atomic. The `put` is
atomic. The line between them is where every other thread's `get` also lands, and the map has no
way to know those two calls were meant to be one.

Measured: **26443 of 200000**, and between 7617 and 36064 over sixteen runs. That is a worse loss
rate than the unsynchronized `int` in block 1, because the window is two map operations wide rather
than three instructions.

**This is the highest-frequency mistake in a graded concurrency leg.** It survives review because
the declared type contains the word `Concurrent`, and the reviewer's eye stops there.

### `Merge`

```java
counts.merge(message, 1, Integer::sum);
```

**One call, so there is no window to lose.** The remapping function runs while the map holds that
bin's lock, which is the guarantee `get` followed by `put` cannot offer at any level of care.
200000 on every run.

**The family, and it is worth memorising as a set.** `merge`, `compute`, `computeIfAbsent`,
`computeIfPresent`, `putIfAbsent`, `replace`. If the new value depends on the old one, the answer is
one of those six.

**The cost, so it is not applied blindly.** The remapping function runs while a bin lock is held,
so it must be short, must not block, and must not touch the same map. A slow function inside
`compute` serialises every thread hashing to that bin.

---

## `RepeatFilter.java`

This class is the corpus's real answer, not an illustration.
`corpus/logger/curveballs/02-collapse-repeats/reference-patch/` absorbs the collapse requirement in
**10 added lines in one file**, measured, and the whole of it is the `Atomic` version below.

### `Atomic`

```java
private final AtomicReference<String> lastKey = new AtomicReference<>();

public boolean accept(String key) {
    return !key.equals(lastKey.getAndSet(key));
}
```

**`getAndSet` replaces the key and returns the previous one, with nothing in between.** So the
question "was I the first to see this key?" gets a truthful answer per thread, even when 200 threads
ask at once. Block 8 prints `accepted: 1` on every run, and that is a guarantee: of a crowd all
offering the identical key, exactly one thread can receive a different previous value.

**`std::atomic<T>::exchange` is the same operation.** `AtomicReference` swaps a reference and never
the object behind it, which is why the value stored has to be immutable. A `String` is; a
`StringBuilder` would give you a published object two threads can both mutate.

**Why one field and not two.** The real requirement keys on level and message together. Two fields,
`lastLevel` and `lastMessage`, are `TwoAtomics` again: a thread can read one and have the other
change under it. The patch composes them into one `String` key, which turns compare-and-replace
into a single indivisible step. That reasoning is stated in the patch's own notes.

### `GetThenSet`

```java
String previous = lastKey.get();
lastKey.set(key);
return !key.equals(previous);
```

**Both calls are atomic, the field underneath is volatile, and nothing here is stale or torn.** The
bug is the gap, and it is the same gap as `RepeatCounts.GetThenPut`. Observed accepted counts across
sixteen runs: 2, 3, 7, and once the correct answer of 1.

**A duplicate delivered twice is a requirement violation, not a rounding error.** Curveball 02's
suite asserts that a repeat reaches no destination at all, so a second delivery fails the leg.

---

## `StopFlag.java`

```java
private boolean stopped;

public long spinUntilStopped() {
    long spins = 0;
    while (!stopped) {
        spins++;
    }
    return spins;
}
```

**Nothing in this thread writes `stopped`, so the JIT may read it once and cache it in a register.**
That is a legal transformation for a non-volatile field, and it is why the loop never exits after
`stop()` is called from another thread. Block 9, all sixteen runs:

```
  boolean stopped          reader still spinning 2s after stop(): true
```

**The spinner is a daemon thread, which is the only reason the program terminates.** A non-daemon
thread in that state keeps the JVM alive after `main` returns. Worth knowing, because a background
worker with a plain stop flag is a process that will not shut down.

```java
private volatile boolean stopped;
```

**One keyword and the loop exits.** The write happens-before every later read, so the read cannot
be hoisted out and cannot be stale. `false` on all sixteen runs.

**This is `volatile`'s whole job, and the shape is narrow.** One writer, one plain assignment that
does not read the current value, no other field changing with it. `curveballs/01-async-appenders/`
adds one background thread serving asynchronous destinations, and a flag telling that thread to
stop would be this. Anything you increment needs an atomic; anything with a sibling field needs a
lock.

---

## `Main.java`

```java
private static void contend(int threads, Runnable task) {
    ...
    ready.await();
    go.countDown();
```

**Three latches, so the threads collide instead of drifting past each other.** Every worker counts
down `ready` and then blocks on `go`, so no thread starts work until all 200 are at the line. Start
200 threads without this and the early ones finish before the late ones begin, and every counter
prints 200000 while still being broken. This is the same design as `harness/DojoConcurrency.java`,
which the graded suites use.

```java
    observer.join();
    return result.get(0);
```

**The observer writes to a plain `ArrayList` and `Main` reads it after `join()`.** No lock, no
concurrent collection, and it is correct. `Thread.join()` is a happens-before edge: everything the
joined thread did is visible to the joiner. Blocks 1 to 3 rely on the same edge to read a plain
`int` field after the pool drains.

**So `join` is the cheapest synchronization in Java, and it is the one people forget they are
using.** If a value is only read after the threads that wrote it have finished, it needs nothing at
all. The moment a reader arrives while writers are still running, you are back to block 4.
