# From C++ — the same guarded destination, and where the mapping stops being one-to-one

You know this material as `std::mutex`, `std::lock_guard` and `std::atomic`, and most of it
transfers. Three things do not, and each one produces working-looking Java that a 200-thread
stress suite takes apart. This page is the mapping first, then those three.

## The same destination, both languages

A destination that keeps two numbers about itself: how many lines it took, and how many characters
those lines came to. Every line in the demo is 38 characters, so the two numbers are related, and
`bytes == lines * 38` is the invariant.

**C++ — a mutex you declared, and a guard with a scope**

```cpp
class TailAppender {
public:
    void write(const std::string& line) {
        std::lock_guard<std::mutex> guard(mutex_);
        ++lines_;
        bytes_ += line.size();
    }

    Tally tally() const {
        std::lock_guard<std::mutex> guard(mutex_);
        return Tally{lines_, bytes_};
    }

private:
    mutable std::mutex mutex_;
    int lines_ = 0;
    long bytes_ = 0;
};
```

**Java — a lock you declared, and a block with a scope**

```java
public static final class Guarded implements Appender {

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

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

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

Line for line, the two are the same design. `synchronized (lock)` acquires on entry and releases
on every exit, including the exit an exception takes, so it is the `lock_guard` rather than the
`lock()`/`unlock()` pair. `new Object()` is the whole allocation: there is no `std::mutex` type to
name, because every Java object already has a monitor.

Note what the Java version does **not** need. `lines` and `bytes` are plain fields with no
`volatile` and no atomic wrapper. Releasing the monitor and re-acquiring it is itself the ordering
guarantee, so the next thread in reads both writes. Adding `volatile` to a field already guarded
by a lock buys nothing and tells a reader you were unsure which mechanism was doing the work.

## The delta table

| C++ | Java | Watch for |
|---|---|---|
| `std::mutex m;` as a member | nothing to declare; every object has a monitor | the monitor is public, see trap 2 |
| `std::lock_guard<std::mutex>` | `synchronized (lock) { … }` | releases on exception too, no `finally` needed |
| `std::unique_lock` with manual `lock`/`unlock` | `ReentrantLock` with `lock()` and `unlock()` | the `unlock()` must be in a `finally`, see below |
| `m.try_lock()` | `reentrantLock.tryLock()`, and the timed overload | `synchronized` has no try form at all |
| `std::recursive_mutex` | there is no other kind; both Java locks are reentrant | trap 3 |
| `std::atomic<int>`, `fetch_add` | `AtomicInteger.incrementAndGet` / `getAndAdd` | `volatile int` is not this, see trap 1 |
| `compare_exchange_strong` | `AtomicReference.compareAndSet` | returns `boolean`, does not write back the seen value |
| `compare_exchange_weak` in a retry loop | `updateAndGet(fn)` / `accumulateAndGet` | the loop is written for you |
| `std::atomic<T>` for a small struct | `AtomicReference<T>` swapping an immutable object | a reference swap, never a lock-free struct |
| `volatile` (near-useless for threads) | `volatile` gives visibility and ordering, no atomicity | trap 1, the highest-cost confusion here |
| `std::memory_order_relaxed` and friends | no per-operation parameter exists | trap 4 |
| `std::condition_variable` | `lock.wait()` / `notifyAll()`, or `Condition` | spurious wakeups in both, so loop on the predicate |
| `std::shared_mutex` | `ReentrantReadWriteLock`, or `StampedLock` | rarely worth it below heavy read contention |
| `std::lock(a, b)` deadlock-free acquire | no equivalent; you order the locks by hand | one lock is the answer that scores, see the end |
| `std::thread` join | `Thread.join()`, or an `ExecutorService` | both give a happens-before edge |
| `thread_local` | `ThreadLocal<T>` | needs `remove()` on pooled threads, or it leaks |
| a data race is undefined behaviour | a data race gives a stale or unexpected value | no UB, so failures look like wrong numbers |
| `std::map` needs external locking | `ConcurrentHashMap` locks per bin, internally | per-operation only, see trap 5 |

## Trap 1 · Three different meanings of one word

`volatile` is the word that means three things across the two languages, so it is worth writing
all three down.

**C++ `volatile`.** Stops the compiler eliding a load or a store, and does nothing about ordering
between threads or about atomicity. Correct for a memory-mapped register, wrong for anything
shared between threads. You already ignore it for that reason.

**Java `volatile`.** A real threading tool. A write to a volatile field happens-before every later
read of it, and the compiler and CPU may not reorder around it. So a reader never sees a stale
value, and never sees writes that came before it out of order.

**`std::atomic` / Java `Atomic*`.** Adds the thing neither `volatile` gives: read-modify-write as
one indivisible operation.

The measurement, from `worked/`, 200 threads incrementing 1000 times each:

```
--- 1. int written; written++
  expected 200000, got 121835  (lost 78165)

--- 2. volatile int written; written++
  expected 200000, got 51048  (lost 148952)
```

`volatile` lost more on that run, not fewer, and the honest version of that claim needs the spread.
Over sixteen runs the plain field landed between 77175 and 196507, and the volatile field between
47113 and 177229. The volatile field lost more than the plain one in 11 of the 16. Neither reached
200000 once.

The direction is not the lesson and it is not stable, so do not learn it as a rule. What is stable
is that adding the keyword changed nothing about correctness. A plain field lets each core keep the
counter in its own cache; a volatile field forces every access out to shared memory. Both leave the
same window between the load and the store.

So `volatile` is right for a single write read elsewhere, with no arithmetic in between.
`curveballs/01-async-appenders/` runs asynchronous destinations on one background thread, and a
flag telling that thread to stop is that shape. Block 9 of `worked/` runs both versions of it:

```
--- 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
```

The plain version never stops. The reader's loop hoisted the field read out of the loop, which it
is entitled to do, because nothing in that thread writes the field. Fifteen runs, `true` every
time. The volatile version exited inside the two seconds on all sixteen.

**The threshold, since `volatile` is cheap enough to over-apply.** Three conditions have to hold. The field is
written by one party and read by others. The write does not depend on the current value. No other
field has to change with it. Miss any one and you need an atomic or a lock.
`volatile` on a field you also guard with a lock is noise; `volatile` on anything you `++` is a
bug.

## Trap 2 · The lock you did not declare is public

Every Java object has a monitor. `synchronized` on a method is `synchronized (this)` around the
body, and `this` is a reference your callers hold. So any caller can take your lock, from
anywhere, and you cannot see the code that does it:

```java
synchronized (exposed) {          // legal, on any object, from any class
    // your appender's write() now blocks until this block ends
}
```

Block 6 of `worked/` does that to an `ExposedMonitor` whose `write` is a `synchronized` method:

```
--- 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
```

No exception, no warning, no way to find the culprit from inside the class. A private final lock
object cannot be reached by name, so this failure mode does not exist:

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

**The threshold, because `synchronized(this)` is not always wrong.** Use a private final lock
object when any one of these holds:

1. A reference to the object reaches code you did not write. A registered listener, an injected
   callback, a lambda a caller handed you, or anything you return from a getter.
2. The class is public and not `final`. A subclass adding `synchronized` methods silently shares
   your lock, and the two of you can now deadlock without either file mentioning the other.
3. There is more than one independent invariant, so you want more than one lock. `this` gives you
   exactly one.

If none of the three holds, `synchronized` on `this` is defensible and shorter. That case is a
`final` class with a narrow surface and one lock over all of its state, which is what
`corpus/lru-cache/reference/src/LruCache.java` chose. Say which of the two you picked and why, and
either answer holds up.

## Trap 3 · Re-locking a lock you already hold

```cpp
std::mutex m;
m.lock();
m.lock();     // undefined behaviour, usually a hang
```

You have learned to avoid that, which usually means avoiding a public method calling another
public method of the same class. Java removes the hazard: both the object monitor and
`ReentrantLock` count re-entries by the owning thread. `writeWhileHolding` in `worked/` takes the
lock and calls `write`, which takes it again:

```
  hold count seen inside write, called under lock: 2
```

`ReentrantLock.getHoldCount()` reports 2 and the call returns normally. So the C++ habit of
splitting a class into locking public methods and non-locking private helpers is optional here.
It is still worth doing, for a different reason. A public method that takes the lock and calls
another public method that takes it again holds the lock for the length of both. Neither author
chose that critical section.

## Trap 4 · There is no memory order to choose

You will look for the parameter. It is not there.

```cpp
counter.fetch_add(1, std::memory_order_relaxed);
```

Every Java volatile access and every `Atomic*` method behaves as sequentially consistent. There is
no relaxed, no acquire-release pair, and no fence type to pick per operation. That is a language
decision rather than an omission. The cost of the strong default is paid on every atomic access.
What it buys is that no Java concurrency review argues about memory orders.

Two honest footnotes. `VarHandle`, since Java 9, does expose `getAcquire`, `setRelease` and
`getOpaque`, which is where the weaker modes went. No interview asks about it, and the reference
solutions in this corpus have no use for it. Second, JIT compilers do elide the barriers a
particular platform does not need, so the strong default is not always a machine-level cost.

## Trap 5 · A `ConcurrentHashMap` makes operations atomic, not sequences

The name suggests it takes the whole problem away. What it takes away is exactly one thing: each
individual call is atomic and safe from many threads at once. A sequence of calls is yours to make
correct, the same as it was with `std::map` under a mutex.

Counting how often each message arrived, on a `ConcurrentHashMap`:

```java
Integer seen = counts.get(message);
counts.put(message, seen == null ? 1 : seen + 1);
```

The `get` is atomic. The `put` is atomic. The window between them is where every other thread's
`get` also lands. 200 threads, 1000 records each, on a `ConcurrentHashMap`:

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

18803 of 200000, and over sixteen runs the get-then-put version landed between 7617 and 36064. Its
best run kept 18% of the increments, and nothing ever threw. `merge` returned exactly 200000 on all
sixteen, because the remapping function runs while the map holds that bin's lock. There is no
window to lose. The one-call family is `merge`, `compute`,
`computeIfAbsent`, `computeIfPresent`, `putIfAbsent` and `replace`. If your update reads the old
value, one of those six is the answer.

`ConcurrentHashMap` is also not a `Collections.synchronizedMap(new HashMap<>())`. A synchronized
map takes one lock for every operation, which serialises readers against each other. The
concurrent map locks per bin and lets readers run with no lock at all. That is the same reasoning
behind `CopyOnWriteArrayList` in `corpus/logger/reference/src/Logger.java`: pick the structure by
which side is hot.

## The memory model, in one page

Java defines a partial order over actions called **happens-before**. If A happens-before B, then B
sees everything A did. If neither orders the other and both touch the same non-volatile field,
that is a data race and you get no guarantee about what is read.

The edges worth memorising, because they are the ones a design actually uses:

| Edge | The rule |
|---|---|
| Program order | Within one thread, an earlier statement happens-before a later one |
| Monitor | Releasing a monitor happens-before any later acquire of the **same** monitor |
| `volatile` | A write to a volatile field happens-before every later read of that field |
| `Thread.start()` | Everything the starting thread did happens-before the new thread's first action |
| `Thread.join()` | Everything the joined thread did happens-before `join()` returning |
| `final` fields | A correctly constructed object's `final` fields are visible with no lock |
| Transitivity | A before B and B before C gives A before C |

Four consequences that change how you write Java, rather than how you describe it.

**A data race is not undefined behaviour.** In C++ it licences anything. In Java you get a stale
value or an unexpected interleaving, and the program keeps running. That is worse to debug and
better to survive, and it is why the failures in this lesson are wrong numbers rather than
crashes.

**Reads and writes of most fields are indivisible anyway.** No `int`, no reference and no smaller
primitive can be observed half-written, whatever the interleaving. The two exceptions are
non-volatile `long` and `double`, which the specification permits to be read as two halves from
two different writes. Declaring those `volatile` removes that, which is one thing `volatile` gives
you that has no C++ analogue.

**`final` is the safe-publication mechanism J4 pointed here for.** A `Tariff` with three `final`
`long` fields, published to another thread after construction, is readable with no lock and no
`volatile`. The guarantee has one condition, and it is J4's constructor trap: `this` must not
escape the constructor. Leak it from a constructor and the guarantee is void.

**Both stress suites in this corpus rely on `join`, not on locks, to read their results.** Block 1
of `worked/` reads a plain `int` field from the main thread after the writers finish. That read
needs no synchronization, because the pool's threads completing happens-before the main thread
observing them. The same edge is why an `ArrayList` filled by one thread and read after `join()` is
safe.

That is the whole model you need for an LLD round. There is more of it: causality requirements, and
what a correctly synchronized program is allowed to observe. None of that changes a design decision
at this level.

## What the graded concurrency leg is measuring

The suites are real, they run 200 threads, and they were tuned against deliberately broken
versions of the reference. `corpus/lru-cache/reference/DECISION_LOG.md` records the tuning: with
`get` unsynchronized, `CROWD = 4` caught the bug 3 times in 10 attempts, `CROWD = 200` caught it 6
to 9 times in 10. The shipped 20 repetitions caught it on the first repetition in three
consecutive runs.

What scores is not the count of `synchronized` keywords. It is a sentence naming the state and its
lock. `LruCache`'s own javadoc is that sentence:

> Two pieces of mutable state: `values`, the key→value map, and whatever bookkeeping the injected
> `EvictionPolicy` keeps internally. They change together, so they share exactly one lock.

Two consequences follow from having written it down. `get` is `synchronized` too, because
`LruCacheApi` says a read mutates eviction standing, so a read is a write to the state that lock
protects. And `listeners` is deliberately outside that lock, as a `CopyOnWriteArrayList`, because
it is read on every eviction and written almost never.

Both variants that skipped this were built and measured, and the decision log records what they
did. An unsynchronized `get` produced
`capacity must still be the hard ceiling under a mixed read/write storm ==> expected: <20> but was: <119>`.
A plain `ArrayList` for the listeners threw `ConcurrentModificationException` at a caller who only
wanted to insert a key.

The habit that costs you the mark is spreading `synchronized` across methods until the suite goes
green. It usually does go green. Then the question arrives: what does that lock protect? Being able
to answer it is the reason `synchronized (this)` versus a private lock is a design decision rather
than a style choice.
