---
title: 'JVM, Concurrency and Performance'
source: 'https://academia.sh/en/courses/jvm-and-concurrency'
language: en
updated: '2026-09-13T07:01:23+00:00'
license: 'CC BY-SA 4.0'
lessons: 11
outcomes:
    - 'Explains the visibility and ordering guarantees of the Java memory model'
    - 'Selects concurrency tools at the correct level of abstraction'
    - 'Interprets garbage collection behavior by measuring it'
    - 'Determines which thread runs a step by running it'
---

# JVM, Concurrency and Performance

Every step of a Java program has a thread beside it, and which one it is cannot be seen by reading the call: the course measures every mechanism from class loading to future-based composition with a single question — is this step run by the caller, by a thread the program created, or by a party that was never created at all? Memory areas, garbage collection, the memory model, synchronization, executors and virtual threads all pass through the same frame.

## JVM Internals

A class passing through the class loader hierarchy into its runtime, and which thread runs its initialization; the memory areas that decide whether a value is thread-local or shared; who takes on the cleanup of an object that has lost its reachability; and which data, and on which side, gives rise to an optimization decision not found in the class file.

### Class Loading

Source: https://academia.sh/en/courses/jvm-and-concurrency/class-loading

The class loader hierarchy runs on delegation: the class a loader is asked for is, in most cases, defined by the loader above it, and identity is read only through `==`. Three of four triggers run class initialization; one — reading a compile-time constant — never does. When two threads touch the same class at the same time, only one of them runs the initialization block; the other waits for it to finish.

The Standard Library and Streams course closed its last lesson with a guarantee: a single
thread no longer stood behind a behavior, because multiple threads now reached the same
object at the same time. This course asks exactly what that guarantee is, but first there
is a smaller question that still cannot be answered by reading the code alone: which
thread runs this line? The question starts before the first line we write. Even before
`main` begins running, a class has to enter its runtime form — it has to be loaded and
initialized — and a thread does that work too: the thread that first touches the class.

This lesson separates two questions. First: when a class is requested, which loader
defines it? Second: which thread runs the initialization block, and what happens to a
second thread that touches the same class at the same time?

#### The Class Loader Hierarchy and Delegation

When a class is requested, the JVM does not ask a single loader for it; it asks a chain of
loaders. Every loader in the chain has a parent, and at the top of the chain stands the
JVM's bootstrap class loader — this loader is not represented as a Java object, and a call
to `getClassLoader()` returns `null` for it. The loader a class is requested from (the
initiating loader) first hands its own work to its parent: it asks its parent, and the
parent asks its own parent in turn. If some loader in the chain can actually define the
class — if it can make the `defineClass` call — that loader becomes the class's defining
loader. This is the principle of delegation, and it is the default behavior of the
`ClassLoader.loadClass` method: it is inherited without writing a single line, just by
naming a loader's parent.

The result of delegation can split the requester from the definer: a class is asked of one
loader, but if the parent finds and defines it, the initiating loader and the defining
loader are not the same object. This difference is invisible in how the call is written —
`child.loadClass("X")` is the same line under every outcome; which loader defined the
class is visible only by comparing the returned `Class` object's `getClassLoader()` with
`==`.

- **JV1** — Loader identity is read only through `==` comparison; the loader's name,
  identity hash, or `toString` output is never printed anywhere.
- **JV2** — The compiler is invoked from inside the program through the standard
  library's tool provider interface; two small classes are compiled into two separate
  directories, and no source outside those two directories enters the measurement.
- **JV3** — The parent loader in this measurement has no parent of its own; what is
  measured is not how far up the chain climbs, but which layer turns out to be the
  definer.

```java
// Delegating.java — a class loader that runs on delegation: findClass is overridden, loadClass is inherited
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;

class Delegating extends ClassLoader {
    private final Path directory;

    Delegating(Path directory, ClassLoader parent) {
        super(parent);
        this.directory = directory;
    }

    protected Class<?> findClass(String name) throws ClassNotFoundException {
        try {
            byte[] bytes = Files.readAllBytes(directory.resolve(name + ".class"));
            return defineClass(name, bytes, 0, bytes.length);
        } catch (IOException e) {
            throw new ClassNotFoundException(name);
        }
    }
}
```

`Delegating` only redefines `findClass`; `loadClass` is never touched. The bytecode for the
`Shared` class sits only in the directory the parent loader looks at, and the bytecode for
`Custom` sits only in the directory the child looks at. The two directories thus stand for
two separate sources, each visible to a different loader.

```java
// Loaders.java — the initiating loader and the defining loader are told apart by identity comparison
import java.io.PrintWriter;
import java.io.Writer;
import java.nio.file.*;
import java.util.spi.ToolProvider;

public class Loaders {
    static void compile(String source, String className, Path directory) throws Exception {
        Path d = Files.createTempDirectory("source");
        Path f = d.resolve(className + ".java");
        Files.writeString(f, source);
        Files.createDirectories(directory);
        PrintWriter discard = new PrintWriter(Writer.nullWriter());
        ToolProvider.findFirst("javac").orElseThrow()
                .run(discard, discard, "-d", directory.toString(), f.toString());
    }

    public static void main(String[] args) throws Exception {
        Path parentDir = Files.createTempDirectory("parent");
        Path childDir = Files.createTempDirectory("child");
        compile("class Shared { static int value() { return 1; } }", "Shared", parentDir);
        compile("class Custom { static int value() { return 2; } }", "Custom", childDir);

        ClassLoader parent = new Delegating(parentDir, null);
        ClassLoader child = new Delegating(childDir, parent);

        Class<?> c1 = child.loadClass("Shared");
        Class<?> c2 = child.loadClass("Custom");

        System.out.println("loader that defines Shared          : "
                + (c1.getClassLoader() == parent ? "parent" : c1.getClassLoader() == child ? "itself" : "other"));
        System.out.println("loader that defines Custom          : "
                + (c2.getClassLoader() == child ? "itself" : c2.getClassLoader() == parent ? "parent" : "other"));
        System.out.println("root loader's representation (null) : " + (Object.class.getClassLoader() == null));
    }
}
```

```
loader that defines Shared          : parent
loader that defines Custom          : itself
root loader's representation (null) : true
```

The same `child.loadClass(...)` call was made twice, and it came back with two different
answers. `Shared` was asked of the child loader, but the parent was the one that found it —
delegation stopped at the top, and the definer turned out to be `parent`, not `child`.
`Custom` was not in the directory the parent loader looks at; once delegation failed, the
request fell back to the child, and this time the definer was the child itself. The only
difference between the two was that the second class's bytecode was never present at the
parent — nothing in the code branches on this. The call to `Object.class.getClassLoader()`
returned `null`: at the very top of the chain sits the bootstrap class loader, represented
by no Java object at all.

#### Which Thread Runs Class Initialization

Loading — finding a class file and building its runtime representation — and
initialization — giving static fields their first values and running the initialization
block — are two separate events, and they can happen at two separate moments. The JVM
does not initialize a class the moment it is requested, but the moment it is actually
about to be used, and it initializes it exactly once. If two threads touch the same class
at the same time, one of them runs the initialization block; the other waits for it to
finish — the order is not left to the scheduler, this is a guarantee of the language
itself, enforced inside the JVM with an implicit lock.

Which actions actually initialize a class is also invisible from how the call is written:
three of four candidate actions trigger initialization, and one never does.

- **JV4** — Whether a class has initialized is tracked with a counter we build
  ourselves; the counter only advances when the initialization block's body actually
  runs, not when the class merely loads.
- **JV5** — The four triggers are each tried on a separate class, because a class
  initializes at most once; testing the same class with two triggers would make the
  second attempt meaningless.
- **JV6** — Two threads touching the same class at the same time is arranged with two
  `CountDownLatch` instances: one guarantees that both are ready, the other guarantees
  that both are released at the same moment; the order between them is not left to the
  scheduler.
- **JV7** — Both racing threads are registered under the same label ("created"); which
  one wins is not the subject of this measurement — whether the winner is registered at
  all is.

```java
// Init.java — which thread runs class initialization: tracked with our own record
import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;

class Init {
    static final AtomicInteger counter = new AtomicInteger(0);
    static final List<String> seen = Collections.synchronizedList(new ArrayList<>());
    static final Map<Thread, String> labels = Collections.synchronizedMap(new IdentityHashMap<>());

    static void register(Thread t, String label) { labels.put(t, label); }

    static String identify() {
        String l = labels.get(Thread.currentThread());
        return l == null ? "unknown" : l;
    }

    static void observe(String trigger) {
        counter.incrementAndGet();
        seen.add(trigger);
        System.out.printf("%-30s initializer running: %s%n", trigger, identify());
    }
}
```

The four triggers are written into four separate classes; each class's initialization
block only calls `Init.observe`.

```java
// NewInstanceTrigger.java — does creating a new instance trigger initialization
class NewInstanceTrigger { static { Init.observe("new instance creation"); } }
```

```java
// StaticMethodTrigger.java — does calling a static method trigger initialization
class StaticMethodTrigger { static { Init.observe("static method invocation"); } static void method() { } }
```

```java
// StaticFieldTrigger.java — does reading a non-final static field trigger initialization
class StaticFieldTrigger { static { Init.observe("static field read"); } static int value = 5; }
```

```java
// ConstantTrigger.java — does reading a compile-time constant trigger initialization
class ConstantTrigger { static { Init.observe("compile-time constant read"); } static final int CONSTANT = 7; }
```

A fifth class is used across two measurements: first it shows that a class can sit loaded
without being initialized, then that initialization runs exactly once when two threads
touch it at the same time.

```java
// LateInitialized.java — first can sit loaded without initializing, then two threads touch it concurrently
class LateInitialized { static { Init.observe("late-initialized class"); } static int value = 9; }
```

```java
// Triggers.java — how many of the four triggers run initialization, is loading separate from initializing, what does concurrent touch give
import java.util.concurrent.CountDownLatch;

public class Triggers {
    public static void main(String[] args) throws Exception {
        Init.register(Thread.currentThread(), "caller");

        System.out.println("-- four triggers --");
        new NewInstanceTrigger();
        StaticMethodTrigger.method();
        int a = StaticFieldTrigger.value;
        int s = ConstantTrigger.CONSTANT;
        System.out.println("value read for the constant                         : " + s);
        System.out.println("new instance creation triggered initialization      : " + Init.seen.contains("new instance creation"));
        System.out.println("static method invocation triggered initialization   : " + Init.seen.contains("static method invocation"));
        System.out.println("static field read triggered initialization          : " + Init.seen.contains("static field read"));
        System.out.println("compile-time constant read triggered initialization : " + Init.seen.contains("compile-time constant read"));
        System.out.println("initializers run by the four triggers               : " + Init.counter.get());

        System.out.println("-- load without initializing; then two threads touch it concurrently --");
        ClassLoader loader = Triggers.class.getClassLoader();
        Class<?> loaded = Class.forName("LateInitialized", false, loader);
        System.out.println("loaded only, initializers run so far     : " + Init.counter.get());

        CountDownLatch ready = new CountDownLatch(2);
        CountDownLatch go = new CountDownLatch(1);
        Runnable body = () -> {
            ready.countDown();
            try { go.await(); } catch (InterruptedException e) { }
            int x = LateInitialized.value;
        };
        Thread first = new Thread(body);
        Thread second = new Thread(body);
        Init.register(first, "created");
        Init.register(second, "created");
        first.start();
        second.start();
        ready.await();
        go.countDown();
        first.join();
        second.join();
        System.out.println("same Class after both threads touched it : " + (loaded == LateInitialized.class));
        System.out.println("total times the initializer ran          : " + Init.counter.get());
    }
}
```

```
-- four triggers --
new instance creation          initializer running: caller
static method invocation       initializer running: caller
static field read              initializer running: caller
value read for the constant                         : 7
new instance creation triggered initialization      : true
static method invocation triggered initialization   : true
static field read triggered initialization          : true
compile-time constant read triggered initialization : false
initializers run by the four triggers               : 3
-- load without initializing; then two threads touch it concurrently --
loaded only, initializers run so far     : 3
late-initialized class         initializer running: created
same Class after both threads touched it : true
total times the initializer ran          : 4
```

Three of the four candidates actually ran initialization: creating a new instance, calling
a static method, reading a non-final static field. The fourth — reading a compile-time
constant — left no line at all; `ConstantTrigger`'s initialization block never ran, yet the
value read (`7`) came back correct. This line is taken up separately below.

The second block separates loading from initialization. `Class.forName("LateInitialized",
false, ...)` loaded the class — it returned a `Class` object — but the counter stayed at
`3`: the initialization block did not run. Loading can complete before initialization and
independently of it.

The third block sends two threads at the same class at the same time. The `ready` latch
guarantees both are at the starting line; the `go` latch guarantees both are released at
the same moment; which one reaches the read of `LateInitialized.value` first depends on
the scheduler and is not known. The result, though, is deterministic: the initializer ran
**exactly once in total** (the counter went from `3` to `4`, not to `5`), and the thread
that ran it was one of the two registered threads — not `unknown`. The second thread also
read `LateInitialized.value`, but it did not run its own initialization block; it hit the
JVM's implicit lock and waited for the first to finish. Which thread wins can change from
run to run, but the fact that the winner is a registered thread, and that initialization
runs exactly once, does not — which is why the lesson registers both threads under the
same label (JV7).

#### A Bounding Measurement: A Compile-Time Constant Class Never Initializes

The fourth row in the table above was empty: `ConstantTrigger`'s initialization block
never ran, and `Init.observe` was never called. The reason lies in how the `CONSTANT`
field is declared: `static final int CONSTANT = 7;` is a compile-time constant, and the
compiler replaces every place that reads such a field with the constant's own value — the
**value inlining** the Object-Oriented Java course measured. The line `int s =
ConstantTrigger.CONSTANT;` leaves no trace of `ConstantTrigger` in the class file at all;
the value `7` is inlined directly at the call site. Since the class is never touched, there
is nothing to initialize either.

This is the same blind spot of the shared measurement framework, rebuilt inside this
lesson: an empty line does not mean "initialization did not happen" — it means "the
trigger never arose." A measurement cannot conclude "the mechanism does not work" merely
from the absence of an expected line; it first has to confirm that the action meant to
produce that line actually took place. Here the action (reading a field) did take place,
but what got read was not the class — it was the number the compiler put in the class's
place. The absence of an observation is not the absence of a mechanism.

The only difference between `StaticFieldTrigger` and `ConstantTrigger` is a single `final`
keyword; both hold a field of type `int`, and both are read by the same kind of trigger
line. Yet one initializes its class while the other never touches it at all — what decides
this is not the field's type or value, but whether its value is known at compile time.
This distinction comes from the same source as the constant folding measured in the first
lesson of the Java Fundamentals course: the compiler leaves no trace at runtime anywhere it
can compute the result ahead of time.

#### Summary

- The class loader hierarchy runs on delegation: the loader a class is requested from asks its
  parent first, and if the parent finds the class, the parent becomes the definer;
  identity is read only through `==` — no name or number is ever printed.
- The bootstrap class loader is not represented as a Java object; `getClassLoader()`
  returns `null` for it.
- Loading and initialization are two separate events: a class can be loaded without its
  initialization block ever running.
- Three of the four triggers (a new instance, a static method call, reading a non-final
  static field) run class initialization; reading a compile-time constant does not,
  because the value is inlined at the call site and the class is never touched.
- When two threads touch the same class at the same time, only one of them runs the
  initialization block; the other hits the JVM's implicit lock and waits for it to
  finish. Which thread wins can change from run to run, but it is always a registered
  thread, and initialization runs exactly once in total.
- If a measurement's expected line is missing from the output, the first thing to check
  is whether the action meant to trigger it actually occurred; an empty line can mean
  "not triggered" rather than "did not happen."

#### Next Step

This lesson measured whose hands a class enters its runtime through: which loader defines
it, which thread initializes it. But it never asked where the fields of an initialized
class actually sit, or whether a value one thread writes can be seen by another —
`LateInitialized.value` was read, but whether it saw the value some other thread wrote was
never tested. The next lesson measures exactly that: a local variable, a static field, an
object field, and a thread-local value sit in four separate places, and only some of them
are visible outside the thread that wrote them.

### Memory Areas

Source: https://academia.sh/en/courses/jvm-and-concurrency/memory-areas

Three of six values written by the same thread are never visible to another thread, and three are — what decides this is not the value's type but where it sits: a local variable and a parameter sit in the call frame, an object field and an array element sit on the heap, a static field sits in the class's own storage. Being shared is not being visible: without an ordering tool, the same read is undefined.

The previous lesson measured whose hands a class enters its runtime through: which loader
defines it, which thread initializes it. But it never asked where the fields of an
initialized class actually sit, or whether a value one thread writes can be seen by
another. This lesson asks that question: how many threads can see a given value?

The answer cannot be given by looking at the value's type — the same `int` never leaves
the writing thread in one place, yet stands open to every thread in another. What makes
the difference is **where the value sits**.

The importance of this distinction shows up in an ordinary situation: two threads calling
the same method at the same time. Even if two threads call `writeParam(5, ...)` at exactly
the same moment, each keeps its own `p` parameter in its own frame; neither can read the
other's `p` in any way, because the two frames do not sit in separate address spaces but
in separate **frame chains**. This is not an isolation the programmer sets up by doing
something — it is provided by the language itself, with no lock and no keyword required.
Heap sharing works in exactly the opposite direction: an object being shared on the heap
depends on that object's reference having been **handed** to another thread; if the
reference is never shared, an object on the heap can remain visible to only a single
thread. Call-frame isolation is structural; heap sharing is a design decision.

#### Three Places: The Call Frame, the Heap, and the Class's Storage

This topic's catalog subtitle names the memory areas "the stack, the heap, and metaspace."
On this platform, **heap** is also the name of a data structure (a priority tree)
elsewhere in the curriculum, so it is worth being explicit here: everywhere in this lesson,
and from here on, **heap** names the shared memory area, never the priority-tree
structure. All three areas are named among the JVM specification's own runtime data
areas: each thread has its own stack, every thread shares a single heap, and every thread
also shares a single method area. The specification does not say how these three areas are
implemented — only which data belongs to which area; this lesson looks only at that
distinction, not at the implementation.

The runtime places a value into one of three separate areas. When a method is called, that
thread's own **call frame** opens, and the method's local variables and parameters are
written into it; every thread has its own frame chain, and none can reach another's. A
frame also closes when its call ends: if a method calls itself recursively, several frames
of the same method stack up in the same thread, but none of them mixes into another
thread's frame chain. When an object is created with `new`, its fields are written to the
**heap**, and when an array is created, its elements are too; the heap is a single area
shared by every thread, and whichever thread a reference to an object is handed to, that
thread reaches the same fields. A class's own static fields, by contrast, sit apart from
any instance, in an area belonging to the class itself — the JVM specification's **method
area**, known by its current name as **metaspace**; this area, too, is single and shared
among every thread. A static field does not need even a single instance of the class to
exist — its slot in metaspace opens while the class is being prepared, before
initialization has even run, and it is independent of the number of instances.

`ThreadLocal` is a fourth pattern: the value it holds physically sits on the heap, but
every thread reaches its own record — the access path runs through the thread itself.
Because of this, even though it is the heap as a class, it behaves like a value in a call
frame. Their lifetimes are not the same, though: a local variable disappears once its own
frame closes, carrying no trace into the next call. A `ThreadLocal` record, by contrast,
stays alive as long as the thread does — when the same thread calls the same method a
second time, its record is still there, and it reads whatever value was left from the
previous call unless it has been overwritten. Both fall into the "alone" column, because
neither is visible to another thread; but one is born with the call and dies with it,
while the other lives as long as the thread does.

An array element sitting on the heap follows the same rule: as the Java Fundamentals
course established, an array is itself an object, so an array's elements sit on the heap
just like an object's fields do, and whichever thread the same reference reaches, that
thread reaches the same elements.

- **JV8** — A value counts as "shared" only when it is read **after** the writer thread
  is waited for to finish (through `join`); a value read before that does not enter this
  measurement.
- **JV9** — For a local variable and a method parameter, the "alone" result does not
  come from an observation but from an impossibility: there is no address at which
  another thread could read these two values, so not even a call could be written to
  try.
- **JV10** — The six measured values are all written by the same writer thread, in the
  same run, in the same order; none of them comes from a separate run.
- **JV11** — No environment-dependent data is ever read: no stack depth, no heap size,
  no address is printed; identity is read only through value equality (`== 5`).

#### Three of Six Values Are Alone, Three Are Shared

The measurement writes an object (`field` and `array` on the heap), writes a class's
static field (in metaspace), writes a `ThreadLocal`, and writes a local variable and a
method parameter belonging to the same thread. All of this happens in a single writer
thread; the read happens from the main thread, after that thread has finished.

```java
// VisibilityBox.java — carries an object field and an array element
class VisibilityBox {
    int field = 0;
    final int[] array = { 0 };
}
```

```java
// VisibleValues.java — six values in four places: how many are visible outside the writer thread
import java.util.concurrent.CountDownLatch;

public class VisibleValues {
    static int staticField = 0;
    static final ThreadLocal<Integer> threadLocal = ThreadLocal.withInitial(() -> 0);
    static int aloneCount = 0, sharedCount = 0;

    static void writeParam(int p, int[] target) { target[0] = p; }

    static void seenBy(String label, boolean isShared) {
        if (isShared) sharedCount++; else aloneCount++;
        System.out.printf("%-26s %s%n", label, isShared ? "shared" : "alone");
    }

    public static void main(String[] args) throws InterruptedException {
        VisibilityBox box = new VisibilityBox();
        CountDownLatch written = new CountDownLatch(1);
        int[] paramSeen = { -1 };

        Thread writer = new Thread(() -> {
            int local = 5;
            threadLocal.set(5);
            staticField = 5;
            box.field = 5;
            box.array[0] = 5;
            writeParam(5, paramSeen);
            if (local != 5) throw new IllegalStateException();
            written.countDown();
        });
        writer.start();
        written.await();
        writer.join();

        // local variable and parameter: there is no address at which another thread
        // could read them, so not even a call could be written to try.
        seenBy("local variable", false);
        seenBy("method parameter", false);
        seenBy("thread-local value", threadLocal.get() == 5);
        seenBy("object field", box.field == 5);
        seenBy("array element", box.array[0] == 5);
        seenBy("static field", staticField == 5);
        System.out.printf("visibility: %d alone, %d shared%n", aloneCount, sharedCount);
    }
}
```

```
local variable             alone
method parameter           alone
thread-local value         alone
object field               shared
array element              shared
static field               shared
visibility: 3 alone, 3 shared
```

Three of the six values never left the writer thread at all: **three** did. The first two
lines needed no measurement at all: `local` and `writeParam`'s parameter live in the
writer thread's own frame and vanish with it when it dies; there is no variable name, no
field, no method through which the main thread could read them. The `if (local != 5)
throw` line in the code confirms this: `local` itself can only be used inside its own
frame as a check, never carried outside the frame in any form. The `writeParam` call, on
the other hand, did carry the parameter value to the main thread — by **copying** it into
a shared array called `paramSeen` — but what got carried was not the parameter itself,
its copy; the measurement never reads this copy, because the question asked is not "can
the value be copied somewhere else" but "is the place the parameter sits visible to
another thread," and the answer is no.

The third line needed a run, and its result pointed the same way: `threadLocal.get()`
called on the main thread reads its own `ThreadLocal` record, not the writer thread's; the
two threads have two separate values under the same name, and `5 == 0` comes out false.
The last three lines all gave `true`: `box.field`, `box.array[0]`, and `staticField`. The
object sits in a single place on the heap, and both threads carry the same reference; the
static field sits in a single place in the class's own storage, and both threads look at
the same class.

#### The Difference Between an Object's Own Field and the Class's Storage

The previous measurement used a single `VisibilityBox` object; "sitting in one place on
the heap" and "sitting in one place in the class's own storage" can look like the same
thing. The difference appears once two objects are created.

```java
// TwoInstances.java — an object's own field is separate per instance, the class's storage is one place
public class TwoInstances {
    public static void main(String[] args) {
        VisibilityBox first = new VisibilityBox();
        VisibilityBox second = new VisibilityBox();
        first.field = 1;
        second.field = 2;
        VisibleValues.staticField = 99;

        System.out.println("first.field                  : " + first.field);
        System.out.println("second.field                 : " + second.field);
        System.out.println("are the two fields the same  : " + (first.field == second.field));
        System.out.println("two instances exist, staticField is still one: " + VisibleValues.staticField);
    }
}
```

```
first.field                  : 1
second.field                 : 2
are the two fields the same  : false
two instances exist, staticField is still one: 99
```

When two `VisibilityBox` objects were created, the `field` field also produced two
separate copies; one held `1`, the other `2`, and the two are not equal. `field` takes up
one slot on the heap per object — as many copies as objects created. `staticField`, by
contrast, is the `VisibleValues` class's own field and is independent of the number of
objects: it would exist even with zero objects created, and it stays single even once two
objects exist. Of the three lines counted as "shared" in the previous measurement, two
(the object field, the array element) got their sharing **from carrying a reference to
the same object**; the third (the static field) got its sharing **from the class
itself**. Both are shared because each sits in a single place — on the heap or in
metaspace — but that "single place" depends on a different thing in each case: on the
object's identity in one, on the class's identity in the other.

This difference has a direct consequence when designing a new class: code that wants to
make a field deliberately shared declares it `static` — sharing is then established
regardless of how many objects exist; code that wants to keep a field tied only to its own
object leaves it as an instance field and never hands the object to a second thread. In
neither case does the JVM itself make a choice; whether a field is `static`, and whether a
reference is shared, are both decisions written into the source code.

#### A Bounding Measurement: Being Shared Is Not Being Visible

The three "shared" lines above were obtained through a conditional read: the
`writer.join()` call placed an order between the write and the read, and the read only
happened once the write had definitely finished. Without that order — say, if the main
thread read `staticField` right after `writer.start()`, without calling `join` — what the
same three lines would give is **undefined**: the old value the writer thread had not yet
written (`0`) might be seen, or the new value (`5`) might be seen. Writing a single `int`
is atomic in Java, so the value read is never a half-written mess; the real issue is not
atomicity but **ordering** — the compiler and the processor are allowed to reorder
instructions as long as they do not break observable single-threaded behavior, and this
reordering can leave it undecided which write counts as happening before which read,
between two threads. This is not a number to be measured and reported, but a state the
language leaves **undefined**; the lesson therefore never runs such a read, it only names
that it exists — this course's rule is to model a race condition, not to run one.

The static field counted as "shared" because it sits in a single place on the heap and
both threads look at the same address — but sitting in a single place is not a guarantee
that the most recent value at that place will be **seen**. "Sitting in the same place" is
a fact about location, settled at compile time; "being visible" is a fact about ordering,
and it is established only at runtime, depending on whatever ordering tool sits between
the threads. Which rule visibility depends on — which write counts as happening before
which read, how tools like `volatile` and locks guarantee that order — is the subject of
the Java Memory Model lesson in the Concurrency topic; this lesson only shows that
visibility has to be built **from zero**, and that without an ordering tool like `join` it
is zero.

Stack depth and heap size are not measured in this lesson: both are environment-dependent
data that change with the machine they run on. The only thing measured is **how many
threads** a value is visible to — location information (which of the four places) is
settled at compile time and does not change from run to run; visibility information
(whether the most recent value at that location can be read) is only meaningful together
with a correct ordering tool, and this lesson measures it only in the case where that tool
**exists**.

#### Summary

- A value sits in one of three places: a local variable and a parameter in the call
  frame, an object field and an array element on the heap, a static field in the class's
  own storage (metaspace); `ThreadLocal` sits on the heap but is reached only through the
  thread itself.
- On this platform, **heap** is the name used for the memory area; the priority-tree data
  structure that shares the same English name is a separate concept elsewhere in the
  curriculum.
- Three of the six measured values (local variable, parameter, thread-local value) never
  leave the writer thread; three (object field, array element, static field) do.
- For a local variable and a parameter, the "alone" result is not an observation but an
  impossibility: there is no address at which another thread could read them.
- Being shared is not being visible: without an ordering tool like `join`, the result of
  the same read is undefined; that is why the lesson never runs an unordered read.
- Stack depth and heap size are environment-dependent and are not measured.

#### Next Step

This lesson assumed that values sitting on the heap were still **reachable** — the `box`
variable was always kept in hand, no reference was ever dropped, and `first` and `second`
stayed alive in `main`'s own frame until the last line. But the last reference to an
object can also be lost; the program drops an object, no frame and no static field can
reach it anymore, and what remains on the heap is a field nobody can read. The next lesson
measures that situation: which thread takes on the cleanup of an object that has lost its
reachability — not the caller, not a thread the program created, but a party that never
appears in any record at all.

### Garbage Collectors

Source: https://academia.sh/en/courses/jvm-and-concurrency/garbage-collectors

Who takes on the cleanup of an object that has lost its reachability: not the caller, not a thread the program created — a party that never appears in any record at all. Cleanup does not run while a strong reference stands and runs once it is removed; a weak and a phantom reference get enqueued within a few requests; a soft reference resists as long as there is no memory pressure. `System.gc()` is a request, not a command.

The previous lesson assumed that values sitting on the heap were still reachable. But the
last reference to an object can also be lost; the program drops an object, no frame and no
static field can reach it anymore, and what remains on the heap is a field nobody can
read. This lesson asks who takes on the cleanup of that field.

Tracing garbage collection itself — the root set, mark-and-sweep, generational collection
— was established as a concept in the Operating System Concepts course and is not
repeated here. This lesson does not measure the theory, it measures the outcome: which
thread runs the cleanup of an object that has lost its reachability, and how does this
work change with a reference's strength?

The question itself cannot be answered by looking at the program's code. Who creates an
object, what we write into its fields, where we carry its reference — all of that is
visible in the source. But what happens once that object becomes unreachable, which
thread will deal with it, is never written in the source at all; the program can only make
a suggestion, and it can see neither what decides the outcome nor when it decides it.

#### Who Takes On Cleanup for an Object That Has Lost Its Reachability

"Reachability" ties directly to the four places the previous lesson established. An object
is reachable if there is a path to it: that path can start from a local variable in a call
frame, from a class's static field, or from the field of another reachable object. If the
path cannot be built from any of these three starting points, the object counts as
unreachable — the program itself can no longer reach it, but the object keeps sitting on
the heap until someone cleans it up. A registered cleanup action can be attached to an
object: once the object becomes eligible for collection, that action runs. But the thread
that runs the action is neither the caller thread nor a thread the program created itself
— it is a party that never appears in any record at all. This is a third case where the
two-sided distinction (caller / created) the previous lesson built for class initialization
is not enough.

Eligibility for collection itself depends on a single rule: as long as a **strong**
reference to an object stands, the object cannot be collected — this is not a heuristic
decision made by the JVM, it is a guarantee given by the language itself. Once the strong
reference is removed, the object becomes eligible for collection, but the **moment** it
will be collected is not guaranteed; the program can only make a **request**. This
two-part structure — reachability is not a fact fixed at compile time but a state that
keeps changing at runtime, and the decision made from that state's outcome sits outside
the program too — explains why the measurement needs a latch and a loop: the answer to
"has it been cleaned up" is not found in a single read, but in a repeated poll.

- **JV12** — Whether an object has been collected is tracked with a cleanup action we
  build ourselves; the action drops a latch when it runs, and the latch does not drop as
  long as it has not run.
- **JV13** — `System.gc()` is a request, not a command; the measurement repeats this
  request at short intervals and reads, through the latch, whether the request found a
  match — no duration or pause is ever measured.
- **JV14** — The identity of the party that runs the cleanup action is read only through
  our own record; every thread not in the record counts as "unknown."

```java
// Identity.java — which thread runs an action: read with our own record
import java.util.*;

class Identity {
    static final Map<Thread, String> labels = Collections.synchronizedMap(new IdentityHashMap<>());
    static void register(String label) { labels.put(Thread.currentThread(), label); }
    static String read() {
        String l = labels.get(Thread.currentThread());
        return l == null ? "unknown" : l;
    }
}
```

```java
// Cleanup.java — cleanup does not run while a strong reference stands; it runs once removed, and the runner is unknown
import java.lang.ref.Cleaner;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;

public class Cleanup {
    public static void main(String[] args) throws InterruptedException {
        Identity.register("caller");

        Cleaner cleaner = Cleaner.create();
        CountDownLatch done = new CountDownLatch(1);
        String[] whoRan = { null };

        Object object = new Object();
        cleaner.register(object, () -> { whoRan[0] = Identity.read(); done.countDown(); });

        for (int i = 0; i < 3; i++) {
            System.gc();
            boolean ran = done.await(50, TimeUnit.MILLISECONDS);
            System.out.println("strong reference standing, request " + (i + 1) + ": " + (ran ? "ran" : "did not run"));
        }

        object = null;
        while (!done.await(50, TimeUnit.MILLISECONDS)) System.gc();
        System.out.println("once the strong reference was removed   : ran");
        System.out.println("party that ran the cleanup action       : " + whoRan[0]);
        System.out.println("is the runner the caller                : " + "caller".equals(whoRan[0]));
    }
}
```

```
strong reference standing, request 1: did not run
strong reference standing, request 2: did not run
strong reference standing, request 3: did not run
once the strong reference was removed   : ran
party that ran the cleanup action       : unknown
is the runner the caller                : false
```

As long as the `object` variable holding the reference stayed in hand, all three separate
requests went unanswered — this is not an unexpected delay, it is the language's
guarantee: while a strong reference stands, collection **cannot** happen, no matter how
many times it is requested. After the `object = null` line, the loop kept making requests
and this time got an answer. The identity of the party that ran the cleanup action was
read with `Identity.read()`, and it came back "unknown" — the action ran neither in the
`caller` thread that runs `main`, nor is there any explanation for it beyond being a party
the program never created at all. The program never created this thread, never named it,
never registered it; yet it was the one that ran our own cleanup code.

In the previous two lessons, two registered parties were enough: a class's initialization
was run either by the caller or by a thread the program created itself, and neither
lesson ever stepped outside those two. This lesson forces a third party, because the
thread that runs the cleanup action was never produced with `new Thread(...)` anywhere in
this program's lines. The "unknown" label is not a claim about ownership — the thread
itself really exists inside the JVM and really runs, it has no counterpart at all in our
own record. A program's runtime surface is not limited to the threads the programmer wrote
themselves.

#### Four Reference Strengths and the Reference Queue

Access to an object does not come in a single strength either. A **strong** reference, as
seen above, blocks collection entirely. A **soft** reference holds the object, but tells
the JVM it may be dropped under memory pressure. A **weak** reference does not hold the
object at all; the moment no other strong reference remains, it is open to collection. A
**phantom** reference is the weakest of all: a call to `get()` always returns `null`,
**even before** collection — a phantom reference does not exist to read an object, but to
learn the moment it was collected, from a queue.

A reference can be attached to a `ReferenceQueue`; once the object becomes eligible for
collection, the reference itself gets enqueued — the queue is the only way to read the
answer to "which reference has just gone empty," because the code holding the reference
cannot find out on its own the moment it loses the object. The order of the four strengths
is not arbitrary: each shows less resistance than the one before it. A strong reference is
never dropped under any condition; a soft reference is dropped only if the JVM is
genuinely under pressure; a weak reference is dropped at the first opportunity; a phantom
reference never holds anything in the first place, it only carries the news that
collection happened. The measurement below sets up three references of three different
strengths to three target objects, and breaks the last strong link to all of them.

- **JV15** — All three reference objects are set up in the same run, against three
  separate targets; which reference gets enqueued is told apart by object identity
  (`==`), not by order.

```java
// ReferenceExperiment.java — four reference strengths: weak and phantom get enqueued, soft resists
import java.lang.ref.*;

public class ReferenceExperiment {
    public static void main(String[] args) throws Exception {
        ReferenceQueue<Object> queue = new ReferenceQueue<>();

        Object weakTarget = new Object();
        Object softTarget = new Object();
        Object phantomTarget = new Object();

        WeakReference<Object> weak = new WeakReference<>(weakTarget, queue);
        SoftReference<Object> soft = new SoftReference<>(softTarget, queue);
        PhantomReference<Object> phantom = new PhantomReference<>(phantomTarget, queue);

        System.out.println("phantom.get() even before collection: " + phantom.get());

        weakTarget = null;
        softTarget = null;
        phantomTarget = null;

        boolean weakEnqueued = false, phantomEnqueued = false;
        for (int i = 0; i < 50 && !(weakEnqueued && phantomEnqueued); i++) {
            System.gc();
            Reference<?> r;
            while ((r = queue.poll()) != null) {
                if (r == weak) weakEnqueued = true;
                if (r == phantom) phantomEnqueued = true;
            }
            if (!(weakEnqueued && phantomEnqueued)) Thread.sleep(20);
        }

        System.out.println("was the weak reference enqueued    : " + weakEnqueued);
        System.out.println("was the phantom reference enqueued : " + phantomEnqueued);
        System.out.println("weak.get() (after collection)      : " + weak.get());
        System.out.println("was soft ever enqueued             : " + (queue.poll() != null));
        System.out.println("soft.get() (no memory pressure)    : " + (soft.get() != null ? "still reachable" : "null"));
    }
}
```

```
phantom.get() even before collection: null
was the weak reference enqueued    : true
was the phantom reference enqueued : true
weak.get() (after collection)      : null
was soft ever enqueued             : false
soft.get() (no memory pressure)    : still reachable
```

`phantom.get()` returning `null` even on the first line is not the result of collection,
it is the contract itself: a phantom reference never hands the object back, it only says
"collected" by getting enqueued. After the request loop repeated a few times, both the
weak and the phantom reference got enqueued; the weak reference's `get()` also started
returning `null` from that point on — since it never held the object, it became eligible
for collection the moment the strong link broke. The soft reference, across the same
request rounds, never got enqueued at all, and its `get()` still returned the object: the
JVM chose not to break the soft reference as long as memory was not under pressure. The
last strong link to all three targets broke at the same moment; the only thing that made
the difference was the reference's strength.

These three results also explain why the three reference types serve different jobs. A
record that holds an object nowhere else, and only watches whether it "still exists,"
wants a weak reference — the record itself must not extend the object's lifetime. A record
caching a result that can be recomputed but is expensive to compute wants a soft reference
— hold the result while memory is free, drop it once memory tightens. Code waiting for the
news that "the object is now definitely gone" wants a phantom reference — it never wants
the object back, only the news from the queue. All three use the same `ReferenceQueue`
mechanism; only the reference's own strength makes the difference.

This mechanism comes from the same foundation as the `Cleaner` measured in the previous
section: the standard library's own documentation says that `Cleaner` is built on phantom
reachability — every registered cleanup action is tracked in the background with exactly
the phantom-reference-and-queue pair shown above. Both measurements ask the same question
at two separate levels: `Cleanup` measures the question "who runs it" through a ready-made
API, while `ReferenceExperiment` measures "when and at what strength does it run" by
building the underlying mechanism by hand.

#### A Bounding Measurement: A Request Does Not Say When It Will Run

Both measurements share the same pattern: `System.gc()` was not called once, it was
**repeated** inside a loop. The reason for this is not delay but the nature of the call
itself — `System.gc()` is a request, not a command; the JVM may ignore this request,
defer it, or answer it in a different form. A single call going unanswered does not mean
"collection did not happen," it only means "not on this request." This is the same shape
as the blind spot in the first lesson: there, an empty line meant "not triggered"; here,
an unanswered request means not "refused" but "has not found an answer yet." The one
difference is that class initialization, in the first lesson, was something certain to
happen once requested (it would necessarily run if its condition was true); the collection
request here depends on the JVM's own discretion even when its condition is true (even
when the object is genuinely eligible for collection).

This is why the lesson repeats the request until a latch drops, and never measures
**when** it runs — only **who** runs it once it does. This is not arbitrary: "when"
depends on the machine's memory state at that moment and on how much load other programs
share, and it changes from run to run, giving no deterministic number to record. "Who"
does not change: whatever the machine or the load, the party that runs the cleanup action
is always outside the record, because this follows from the JVM's own design, not from
the current load. The distinction is an example of this course's general rule: what
depends on the run (when) stays out of the table; what stays fixed run to run (who, how
many, which party) goes in.

Neither measurement goes beyond this: which collector algorithm ran, how long a pause
lasted, how much a given generation grew, how much memory was reclaimed — none of this is
written in this lesson, because all of it depends on the implementation and on the machine
it runs on. The word "Performance" appearing in the course's name does not change this:
what is counted here is only the party that runs and the number of references that get
enqueued.

#### Summary

- The party that runs the cleanup of an object that has lost its reachability is neither
  the caller thread nor a thread the program created — it is an "unknown" party that
  never appears in any record.
- An object cannot be collected as long as a strong reference to it stands; this is not a
  heuristic but the language's guarantee. Once the strong reference is removed, the
  object opens to collection, but the moment it will be collected is not guaranteed.
- A weak and a phantom reference get enqueued within a few requests after the strong link
  breaks; a soft reference resists as long as there is no memory pressure. A phantom
  reference's `get()` always returns `null`, even before collection.
- `System.gc()` is a request, not a command; that is why the measurement repeats the
  request in a loop and measures only who runs it once it does, not when it runs.
- Generational collection, the root set, and mark-and-sweep theory were not repeated in
  this lesson; this lesson measured only the outcome of reachability — who cleans up, and
  when each reference opens to collection.
- Collector implementation, pause duration, generation size, and the amount of memory
  reclaimed were not written in this lesson; all of it is environment-dependent.
  `Cleaner`, too, is built on phantom reachability with exactly the `ReferenceQueue`
  mechanism used in this measurement — the two measurements are two separate levels of
  the same question.

#### Next Step

This lesson saw a decision that is not found in the class file: which object gets
collected and when was a decision made by the runtime, not by the program itself, and who
runs that decision could not be read from the program's own record — it could only be
measured with our own record. There is another decision not found in the class file — this
time it concerns not cleanup but speed. While a method's body sits unchanged in the class
file, the same method may not run the same way on its first call as on its thousandth. The
next lesson measures that difference: the party that decides is again not the program
itself, and again invisible by reading the code, but this time what it accumulates is not
garbage — it is an observation gathered while running.

### Just-in-Time Compilation

Source: https://academia.sh/en/courses/jvm-and-concurrency/just-in-time-compilation

The class file holds no optimization decision at all: even if the same method is called three hundred thousand times, the instruction list read from the class file stays byte-for-byte identical. The party that makes the decision is not the program itself; the receiver type count the decision rests on, though, is produced by the calling thread while it runs. When a call site moves from monomorphic to polymorphic, the assumption becomes invalid — this lesson does not measure speed, it only counts instructions and receiver types.

The previous lesson saw a decision not found in the class file: which object gets
collected and when. This lesson measures another decision not found in the class file —
this time a decision about speed, not cleanup. While a method's body sits unchanged in the
class file, the same method may not run the same way on its first call as on its
thousandth. The How Computers Work course established just-in-time compilation as a
concept, and this lesson does not repeat it; this lesson only looks at the observed
behavior — does the decision sit in the class file, or somewhere else?

#### The Class File Holds No Optimization Decision

A class file is fixed from the moment it is compiled and written to disk; no runtime event
changes its content. Whether a method is never called or called millions of times, that
method's instruction list in the class file stays the same — because the decision "which
method is called often, when should it be sped up" is not information that belongs to the
class file itself. This is not left as a claim; it is measured below.

This is the fourth repetition of the pattern the previous three lessons built. A loader
does not write which class it defines into the class file; which thread runs class
initialization is invisible in the class file; who collects an unreachable object is not
the class file's business either. The fourth is the same shape: a class file states
exactly what a method does, but how that method will run at runtime — interpreted, or in
a sped-up form — is never part of the class file. All four measurements arrive at the same
conclusion: a class file is a translation of the source, but it is not the runtime itself.

The measurement uses the `Gauge` tool the Java Fundamentals course built — not as a
measurement unit here, only as a tool that reads a method's instruction list from the
class file.

- **JV16** — `Gauge` is only a tool; it reads a method's instruction list from the class
  file, and does nothing like classifying an "added step."
- **JV17** — The same method, after really being loaded and called thousands of times,
  has its instruction list read again from the very same class file; no recompilation is
  done.
- **JV18** — Only the call count is counted (how many times it was called); no duration
  is ever measured, no timestamp is ever read.

```java
// Gauge.java — the tool that reads a method's instruction list from the class file (from Java Fundamentals, as a tool only)
import java.io.PrintWriter;
import java.io.Writer;
import java.lang.classfile.*;
import java.nio.file.*;
import java.util.*;
import java.util.spi.ToolProvider;

class Gauge {
    static Path compile(String source, String className) throws Exception {
        Path d = Files.createTempDirectory("gauge");
        Path f = d.resolve(className + ".java");
        Files.writeString(f, source);
        PrintWriter sink = new PrintWriter(Writer.nullWriter());
        ToolProvider.findFirst("javac").orElseThrow().run(sink, sink, "-d", d.toString(), f.toString());
        return d;
    }

    static List<String> instructions(Path directory, String className, String method) throws Exception {
        ClassModel cm = ClassFile.of().parse(directory.resolve(className + ".class"));
        List<String> a = new ArrayList<>();
        for (MethodModel m : cm.methods())
            if (m.methodName().equalsString(method) && m.code().isPresent())
                for (CodeElement e : m.code().get())
                    if (e instanceof Instruction i) a.add(i.opcode().name().toLowerCase(Locale.ROOT));
        return a;
    }
}
```

```java
// HotPath.java — even if a method is called many times, the instruction list in the class file does not change
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLClassLoader;
import java.nio.file.Path;
import java.util.List;

public class HotPath {
    public static void main(String[] args) throws Exception {
        String source = """
            public class Calculation {
                public static int apply(int x) { return x * 2 + 1; }
            }
            """;
        Path directory = Gauge.compile(source, "Calculation");
        List<String> before = Gauge.instructions(directory, "Calculation", "apply");

        URLClassLoader loader = new URLClassLoader(new URL[] { directory.toUri().toURL() });
        Class<?> calculation = Class.forName("Calculation", true, loader);
        Method method = calculation.getMethod("apply", int.class);

        int callCount = 300_000;
        int total = 0;
        for (int i = 0; i < callCount; i++) total += (int) method.invoke(null, i);

        List<String> after = Gauge.instructions(directory, "Calculation", "apply");

        System.out.println("call count                                : " + callCount);
        System.out.println("instruction list before the calls         : " + before);
        System.out.println("instruction list after the calls          : " + after);
        System.out.println("are the two lists byte-for-byte identical : " + before.equals(after));
        System.out.println("is the total nonzero (it ran)             : " + (total != 0));
    }
}
```

```
call count                                : 300000
instruction list before the calls         : [iload_0, iconst_2, imul, iconst_1, iadd, ireturn]
instruction list after the calls          : [iload_0, iconst_2, imul, iconst_1, iadd, ireturn]
are the two lists byte-for-byte identical : true
is the total nonzero (it ran)             : true
```

The `apply` method was called three hundred thousand times, through a genuinely loaded
class — `total` did not come out zero, so the calls really ran. Yet the same class file
was read separately before and after the calls, and the two instruction lists came out
byte-for-byte identical. This is not "unchanged because it was not measured" — same file,
same method, two separate reads, the same result. Noticing that the method is called
often, deciding to speed it up, actually applying that decision — none of this is written
into the class file. The decision does not exist in the program's own source text, nor in
the class file it compiles to; it is born at runtime, in a place the program itself never
created.

As the How Computers Work course established, the virtual machine can either interpret
bytecode directly or translate part of it into instructions specific to the processor it
is running on; which path is chosen depends on how often that part runs. But this
translation stays where it was produced — in the running program's own memory; it is never
written back to the source or to the class file. The measurement below tests this
distinction directly: the same class file, even after being brought into the state most
primed for speeding up, is compared against its own form on disk.

Making the measurement through `Method.invoke`, an indirect call, is a deliberate choice:
the `Calculation` class was not compiled into the same directory as this lesson's other
classes; it was loaded with a separate loader, and its name was carried only as a piece of
text. The reason is being able to both really run the same class file and read it again
after the calls; if the class had been written as an ordinary neighboring file, reading the
running copy's bytecode separately would still require going through the same path. What
gets read is not merely a copy recompiled from the same source, but the very class that was
really called three hundred thousand times.

#### The Receiver Type Passing Through a Call Site: From Monomorphic to Polymorphic

A decision needs data to rest on, and that data has to come from somewhere. Every call
made through an interface — a **call site** — can accumulate a record, over time, of which
concrete types it has encountered. This lesson keeps that record with its own counter: it
counts, through `getClass()` identities, how many distinct types the receivers passing
through a call site come from. A call site counts as **monomorphic** if it has only ever
encountered a single type, and **polymorphic** if it has encountered more than one.

The importance of this distinction comes from here: the class-file counterpart of a
`Processable i` reference is the call `i.value()`, and this call does not say at compile
time which concrete class it carries — it could be `TypeA`, it could be `TypeB`. If a call
site, in practice, always meets the same single type, that fact is an opportunity to speed
things up: instead of re-asking "which class is this" on every call, it can be assumed
that the same answer has always come back in the past. But this assumption is not part of
the class file — because at the moment the class file is compiled, how many distinct types
the call site will meet at runtime is not yet known. How many implementations the
`Processable` interface has does not give this information either: an interface can have
ten implementations, but a single call site may see only one of them over the course of a
run; the receiver count is not a property of the interface itself, but a summary of what
that particular call site has seen in the past.

- **JV19** — The number of receiver types passing through a call site is counted with a
  set we build ourselves (through `getClass()` identities); this is the result of our
  own counter modeling that mechanism, not of the runtime's own profiling machinery.

```java
// Processable.java — the common interface for receivers passing through a call site
interface Processable { int value(); }
```

```java
// TypeA.java — the first receiver type at the call site
class TypeA implements Processable { public int value() { return 1; } }
```

```java
// TypeB.java — the second receiver type, added to the call site later
class TypeB implements Processable { public int value() { return 2; } }
```

```java
// CallSite.java — a single call site through the interface
class CallSite { static int call(Processable i) { return i.value(); } }
```

```java
// ReceiverType.java — the number of distinct receiver types passing through a call site, counted with our own set
import java.nio.file.Path;
import java.util.*;

public class ReceiverType {
    public static void main(String[] args) throws Exception {
        Set<Class<?>> seenTypes = new HashSet<>();

        TypeA a = new TypeA();
        int callCount = 100_000;
        for (int i = 0; i < callCount; i++) {
            seenTypes.add(a.getClass());
            CallSite.call(a);
        }
        System.out.println("call count (TypeA only)      : " + callCount);
        System.out.println("monomorphic call site        : " + (seenTypes.size() == 1));

        TypeB b = new TypeB();
        seenTypes.add(b.getClass());
        CallSite.call(b);
        System.out.println("second implementation loaded : " + (seenTypes.size() == 2));
        System.out.println("polymorphic call site (assumption invalid): " + (seenTypes.size() > 1));

        String source = """
            interface Processable { int value(); }
            class CallSite { static int call(Processable i) { return i.value(); } }
            """;
        Path directory = Gauge.compile(source, "CallSite");
        System.out.println("instruction list for the call method: " + Gauge.instructions(directory, "CallSite", "call"));
    }
}
```

```
call count (TypeA only)      : 100000
monomorphic call site        : true
second implementation loaded : true
polymorphic call site (assumption invalid): true
instruction list for the call method: [aload_0, invokeinterface, ireturn]
```

Across a hundred thousand calls, `CallSite.call` met only `TypeA` instances; the counter
stayed at one. The moment `TypeB`'s first instance passed through the call site, the
counter rose to two and the call site became polymorphic. A real runtime can speed up a
monomorphic call site based on exactly this information — by assuming the receiver will
always be the same type; once a second type appears, this assumption becomes invalid and
the runtime deoptimizes. This lesson does not capture that reversal as a real event (doing
so would require an implementation-specific tool); it only models, with its own counter,
the condition that invalidates the assumption — the second type joining the call site. The
instruction list of the underlying `call` method, though, is identical in both cases: the
`invokeinterface` instruction does not say which concrete type is coming, it only says
"call a `Processable`"; whether the receiver count is one or two leaves no trace in the
class file.

Who produced the data this decision rests on? The code made its own addition to the
`seenTypes` set before every call — this addition is part of the calling thread's own
code, not something done by a separate party. The real runtime's profile data is produced
on the same principle: every call passing through a call site leaves data for the party
that will make the decision (the runtime itself), and the step that accumulates this data
is itself part of the code the calling thread executes. The party that makes the decision
is not the caller, but the caller is what produces the data the decision rests on.

This distinction resembles the "who runs it" versus "who sees it" distinction from an
earlier lesson, but it is not the same. There, the one who wrote a value and the one who
saw it were two separate threads; here, the one who produces the data (the caller) and the
one who decides based on that data (the runtime) are two separate **parties**, and neither
can be named with the same concept of a thread — because the party that decides is never
observed in this lesson through a thread identity, it is recognized only by its absence
from the class file.

#### A Bounding Measurement: A Hot Path Is Not a Claim About Speed

No duration was measured anywhere in this lesson, and none can be: how long three hundred
thousand calls took, which of a monomorphic and a polymorphic call site runs faster —
none of this is written in this lesson, because both depend on the machine it runs on, on
the current load, and on the implementation. The only things counted are the instruction
count and the receiver type count; both are data the program produces itself, staying
fixed from run to run.

The term **hot path** is therefore not a claim about speed, but a region defined by call
density: a method is hot because it is called often, not because it runs fast. The
measurement above put the `apply` method into a dense call region by calling it three
hundred thousand times, but this lesson never compared any duration before or after the
calls — only the call count and the fact that the class file did not change were counted.
Whether this heat turns into a speed gain is not this lesson's question.

This boundary works in both directions. On one hand, no claim was written about whether
the three hundred thousand calls were "slow" or "fast" — the only claim was that the class
file did not change. On the other hand, no claim was written about a monomorphic call site
running "faster" than a polymorphic one either — the only thing measured was whether the
receiver count was one or two. Neither measurement came anywhere near the question of "how
fast," answering only the questions of "how many" and "is it the same"; this course's
measurement discipline applies here in exactly the same way.

#### Summary

- A class file is fixed once compiled: even if a method is called three hundred thousand
  times, its instruction list stays byte-for-byte identical before and after the calls.
- The decision of when and how to speed up a method is never in the class file; the
  decision is born at runtime, in a place the program itself never created.
- A call site counts as monomorphic or polymorphic based on how many distinct types the
  receivers passing through it come from; once a second implementation joins the call
  site, the monomorphic assumption becomes invalid — this lesson models that
  invalidation (deoptimization) with its own counter, it does not capture it as a real
  event.
- The party that produces the data (the receiver type count) a decision rests on is the
  calling thread; the party that makes the decision is not the caller.
- Whether a call site's receiver count is one or two, the instruction list of the method
  carrying that call does not change in the class file.
- A hot path is defined by call density, not by duration; this lesson never measures
  duration, it only counts instructions and receiver types.

#### Next Step

Across this topic, three decisions not found in the class file were observed: which loader
defines a class and which thread initializes it, who collects an unreachable object, and
when a method gets sped up. In all three, the party that makes the decision was not the
program itself — it could be found nowhere, neither in the source text nor in the class
file compiled from it. But the program itself is still what creates the threads that run
its code: the program itself writes the `new Thread(...)` line, the program itself makes
the `start()` call. The next topic starts from there — how is a thread brought into
existence from inside the program, and which party runs its body?

## Concurrency

The call that decides whether a body runs on the caller or on another thread, the memory model's promises of visibility and ordering, measuring monitor locks and reentrancy, which side concurrency utilities load the work onto, who an executor pool hands a task to once it is full, the distinct identity count of virtual threads, and who runs the step in future-based chaining.

### Threads and Lifecycle

Source: https://academia.sh/en/courses/jvm-and-concurrency/threads-and-lifecycle

The same body runs on the caller with run(), on a thread created with start(); the difference is a single method name. Of three interruption setups, only one stops the body — the other two reach the same next step whether the interrupt call arrives or not.

The Just-in-Time Compilation lesson ended with this result: the party that decided on an
optimization not present in the class file was not the program itself, but the program was
still the one that created the threads that ran the code. This lesson starts from there. Who
loads the class file, who makes the decision, who produces the profile — every one of those
questions assumed a body was **already** running. Now the lesson takes a step back: how does a
thread come to exist, and which party runs the body that was just written for it?

The answer does not show up in the call syntax. A `Runnable` body is defined in exactly one
place and can be triggered by two different methods; both run the same line, and both are
valid calls as far as the compiler is concerned. The first difference this lesson measures —
also the first two lines of the course's shared core — sits exactly here.

The course's question has two parts: which thread runs a given step, and who created that
thread? This lesson looks only at the second part, at the act of creating itself. While a
program runs, at least one thread already exists — the thread running the `main` method — and
if the program never asks for another thread, that one is enough on its own. Asking for a new
thread moves the body to a separate flow; that flow is no longer the **caller's** flow, and it
starts and ends on its own. The two sections below measure this process of creation and
completion, together with a third question in between: what does telling an unfinished thread
"stop now" actually make the body do.

#### Same Body, Two Ways to Run It

- **CC1** — The label that says who is running is read not from the thread itself but from a
  separate registry; if a thread never registered with `Run.announce`, it counts as `unknown`.
- **CC2** — The thread that runs `Bodies.main` is registered as `"caller"` from the start; for
  the rest of this lesson, "the caller" always means this thread.

```java
// Run.java — labels which thread runs a given body
import java.util.*;

public class Run {
    static final Map<Thread, String> labels =
            Collections.synchronizedMap(new IdentityHashMap<>());

    static void announce(String label) { labels.put(Thread.currentThread(), label); }

    static String who() {
        String l = labels.get(Thread.currentThread());
        return l == null ? "unknown" : l;
    }

    static void observe(String point) {
        System.out.printf("%-32s %s%n", point, who());
    }
}
```

`Run` never reads a thread's name, identity hash, or priority — all of that is
environment-dependent data. The only thing it reads is a label written in advance by
`announce`; the question of identity is reduced to the name we ourselves gave it.

```java
// Bodies.java — the same body runs on the caller via run(), on the created thread via start()
public class Bodies {
    public static void main(String[] args) throws InterruptedException {
        Run.announce("caller");
        Runnable body = () -> Run.observe("body");

        // 1. direct call: run() does not bind another thread, the caller runs it
        new Thread(body).run();

        // 2. start(): the created thread runs the body
        Thread t = new Thread(() -> { Run.announce("created"); body.run(); });
        t.start();
        t.join();
    }
}
```

```
body                             caller
body                             created
```

Both lines run the same `body` reference, and both call `Runnable.run()`. The difference is who
says `run()`: on the first line, `run()` is called directly on the `new Thread(body)` object —
this is an ordinary method call, it does not **create** a new thread, it only runs the body on
the caller's own stack. On the second line, `start()` is called; this sends a request to the
runtime for a new thread, and the body runs on that thread. Without `t.join()`, nothing would
guarantee that `main` waits for the body to finish — this bridge is treated separately in the
limiting measurement below.

The expression `new Thread(body)` itself has not **run** anything yet. In both ways of running
it, a `Thread` object is built first; the moment the object is built, it carries only a
record — there is yet no commitment about who will run the body. On the first line, that
object is called with `run()` without ever being `start()`ed — the object exists but a thread
was never requested, the body enters the caller's own code flow directly. On the second line,
the `start()` call makes that commitment: it asks the runtime for a new thread, and the body now
runs on the thread that answers that request. The difference is between a call that **creates**
a thread and one that does not — and that difference stays hidden behind a `run()` call that
looks identical in both lines.

#### Lifecycle: Deterministic Observations

- **CC3** — `isAlive()` reads no identity data from the thread itself; it only answers
  "was it started and has it not finished yet" with `true`/`false`, and this does not depend on
  the environment.

A `Thread` object is **new** before `start()` is called, is **running** after `start()` and
before it finishes, and is definitively **finished** the moment `join()` returns. All three
states are observed deterministically with `isAlive()`; the transitions between them depend not
on timing but on which method was called. `isAlive()` itself also reads no environment-dependent
data from the thread — the only thing it returns is a boolean, and that value is determined by
which methods were called before it, not by how busy the machine happens to be at that moment.

```java
// Lifecycle.java — states observed deterministically: before start, while running, after finish
import java.util.concurrent.CountDownLatch;

public class Lifecycle {
    public static void main(String[] args) throws InterruptedException {
        CountDownLatch start = new CountDownLatch(1);

        Thread t = new Thread(() -> {
            try { start.await(); } catch (InterruptedException e) { }
        });

        System.out.println("before start   isAlive: " + t.isAlive());
        t.start();
        System.out.println("after start    isAlive: " + t.isAlive());
        start.countDown();
        t.join();
        System.out.println("after join     isAlive: " + t.isAlive());

        try {
            t.start();
            System.out.println("second start   : succeeded");
        } catch (IllegalThreadStateException e) {
            System.out.println("second start   : " + e.getClass().getSimpleName());
        }
    }
}
```

```
before start   isAlive: false
after start    isAlive: true
after join     isAlive: false
second start   : IllegalThreadStateException
```

`start` holds the body on a `CountDownLatch`; this guarantees, **without leaving it to a race**,
that the body has not yet finished once `start()` returns. The `join()` call does two things at
once: it stops the caller until the body finishes, and it guarantees that `isAlive()` will be
`false` after it returns — `join` is this lesson's ordering tool. The last line shows a separate
rule: a `Thread` object can be started only **once**; a second `start()` call always throws
`IllegalThreadStateException`, regardless of the object's state.

The reason for this restriction is the lifecycle itself. `start()` moves a `Thread` object out
of the **new** state and hands it to the runtime exactly once; after that handoff, the object
never returns to the **new** state again, and once it finishes it is considered permanently
over. Allowing an object to be started a second time would mean reusing the same thread identity
across two separate runs; the framework instead requires a fresh `Thread` object to be built for
every run. Code that wants to rerun a finished body does so by wrapping the same `Runnable` body
in a **new** `Thread` object — the object changes, the body does not.

#### Interruption: One Request, the Answer Lives in the Body

- **CC4** — Interruption is not a command; `Thread.interrupt()` only sets a flag or drops an
  exception in a blocking call. What the body **does** in response to that request is written
  in the body's own code — it is not something the framework decides.

Three setups share the same pattern: a thread starts, registers itself with `CountDownLatch`,
then an interruption request is (optionally) sent, and finally its completion is awaited with
`join()`. The only thing that changes is **how** the body responds to the interruption.

The first body's loop condition calls `Thread.currentThread().isInterrupted()`; this is an
instance method and it does **not clear** the flag after reading it. There is also a static
counterpart that asks the same question but clears the flag as soon as it reads it; that
counterpart is deliberately not used here, because the `result.append` line after leaving the
loop might still need the information "this thread was asked to interrupt" — the clearing form
would wipe the flag on the first read and would not carry that information past the loop. Which
form to use is also the body's decision — the framework offers both, and afterward the choice
falls to the programmer depending on whether the flag is still needed.

```java
// Interruption.java — interruption is a request: the body can see it but its own code decides whether to stop
import java.util.concurrent.CountDownLatch;

public class Interruption {
    static String stopsCheckingFlag(boolean doInterrupt) throws InterruptedException {
        CountDownLatch started = new CountDownLatch(1);
        StringBuilder result = new StringBuilder();
        Thread t = new Thread(() -> {
            started.countDown();
            while (!Thread.currentThread().isInterrupted()) { }
            result.append("left the loop on seeing the flag");
        });
        t.start();
        started.await();
        if (doInterrupt) t.interrupt();
        t.join();
        return result.toString();
    }

    static String catchesInSleep(boolean doInterrupt) throws InterruptedException {
        CountDownLatch started = new CountDownLatch(1);
        StringBuilder result = new StringBuilder();
        Thread t = new Thread(() -> {
            started.countDown();
            try { Thread.sleep(80); result.append("sleep completed, "); }
            catch (InterruptedException e) { result.append("exception caught, "); }
            result.append("moved to the next step regardless");
        });
        t.start();
        started.await();
        if (doInterrupt) t.interrupt();
        t.join();
        return result.toString();
    }

    static String neverAsksFlag(boolean doInterrupt) throws InterruptedException {
        CountDownLatch started = new CountDownLatch(1);
        StringBuilder result = new StringBuilder();
        Thread t = new Thread(() -> {
            started.countDown();
            long total = 0;
            for (long i = 0; i < 20_000_000L; i++) total += i;
            result.append("loop completed (total greater than zero: " + (total > 0) + ")");
        });
        t.start();
        started.await();
        if (doInterrupt) t.interrupt();
        t.join();
        return result.toString();
    }

    public static void main(String[] args) throws InterruptedException {
        System.out.println("1. body checking the flag, with interruption:");
        System.out.println("   " + stopsCheckingFlag(true));
        System.out.println();

        System.out.println("2. body catching in sleep, with interruption:");
        System.out.println("   " + catchesInSleep(true));
        System.out.println("2. same body, WITHOUT interruption:");
        System.out.println("   " + catchesInSleep(false));
        System.out.println();

        System.out.println("3. body never asking the flag, with interruption:");
        System.out.println("   " + neverAsksFlag(true));
        System.out.println("3. same body, WITHOUT interruption:");
        System.out.println("   " + neverAsksFlag(false));
    }
}
```

```
1. body checking the flag, with interruption:
   left the loop on seeing the flag

2. body catching in sleep, with interruption:
   exception caught, moved to the next step regardless
2. same body, WITHOUT interruption:
   sleep completed, moved to the next step regardless

3. body never asking the flag, with interruption:
   loop completed (total greater than zero: true)
3. same body, WITHOUT interruption:
   loop completed (total greater than zero: true)
```

The first setup's loop **only** looks at the interruption flag; if the interrupt call never
came, this loop had no other way out — the code itself shows this, which is why that setup is
never run without an interruption. The second and third setups were each run **twice**, once
with the interrupt call and once without, and both reached the same result either way: the catch
block inside `Thread.sleep` swallows the exception and moves to the next line, while the
counting loop never reads the flag at all and completes unaware that an interruption ever
happened. Of the three setups, **only one** never finishes its body without an interruption
request; the other two reach the same next step whether they see the interruption request or
not. Interruption itself does not impose a behavior — the body's author chooses the behavior.

The distinction is not specific to these three examples; it generalizes into a rule.
Standard-library **blocking** calls — `Thread.sleep`, the wait method called while holding an
object lock, `join`, the waiting calls in the concurrency package — check the interruption flag
themselves and, when the flag is set (or the moment it becomes set), throw
`InterruptedException` and **clear** the flag. Every other kind of code — a computation loop,
writing to a file, reading a network socket — does not check the flag on its own; a body that
wants to check it has to write that check by hand, as in the `first setup`. The second setup
sits in the middle of this distinction: `sleep` sees the flag and throws the exception, but the
body catches that exception and continues on its way without doing anything about it. The
exception being thrown is a guarantee; **responding to it** is the body's responsibility, and
that responsibility is lost in an empty `catch` block more often than not.

#### Limiting Measurement: start() Returning Gives No Order

When the `t.start()` call in `Bodies.main` returns, nothing guarantees that the body's first
line has already run. `start()`'s job is to **request** a new thread; when that request will be
fulfilled is outside the caller's knowledge. This is why, if a line were added inside
`Bodies.main` to read the body's output before `t.join()` was called, whether that line ran
before or after the body's output would be an **observation with no order fixed by the setup**
and would not count as a measurement — much like this lesson's shared definition of the blind
spot, where "did not happen" gets confused with "the order was never established." Every setup
in the `Lifecycle` and `Interruption` lessons therefore opens with a `CountDownLatch`: the body
registers itself first, and only after that registration does the caller move to the next step.
The order is not left to the scheduler; it is built by hand with `await`, `join`, and
registration points.

This limit also explains why the measurement always carries the same skeleton.
`Interruption.stopsCheckingFlag`'s body registers itself with `started.countDown()` before the
main thread waits on `started.await()`; only after that registration is the interruption request
sent. If the registration line were skipped and `t.interrupt()` were called right after
`t.start()`, whether the request arrived before or after the body entered the `while` loop would
be undecidable — the result would not change (the loop would sooner or later see the flag and
exit), but **which turn** it exited on could not be measured. This lesson never claims a turn
count; the claim is limited to the question **did the output appear or not**, because that is
exactly what the boundary is: what can be measured is only as much as the setup makes
deterministic.

#### Summary

- The same `Runnable` body runs on the caller if called with `run()`, and on a created thread
  if called with `start()`; the difference is a single method name in the call syntax.
- `isAlive()` deterministically observes a thread's state before starting, while running, and
  after `join()`; a second `start()` call always throws `IllegalThreadStateException`,
  regardless of the object's state.
- Interruption is a request, not a command: of three setups, only one (the one checking the
  flag in its loop condition) never finishes its body without an interruption request; the
  other two reach the same next step whether they see the request or not.
- `start()` returning does not mean the body's first line has run; no order is established
  between the two events, and this cannot be observed without a setup — the order is built by
  hand with `CountDownLatch` and `join`.

#### Next Step

This lesson answered the question "who is running" and showed that the answer to an
interruption request is written in the body's own code. But in the `Interruption` setups, every
setup read a `StringBuilder` and a counter written by the body without using any synchronization
at all, because the same thread always both wrote and read the value — `started.await()` and
`t.join()` only established **order**, and the real mechanism that provided visibility was a
guarantee each of them already carried internally, never named in this lesson. The next lesson
removes this comfort: when the thread writing a value and the thread reading it are different,
what is guaranteed, and which side — only the writer, only the reader, or both — is required to
establish that guarantee?

### Java Memory Model

Source: https://academia.sh/en/courses/jvm-and-concurrency/java-memory-model

Of eight publication mechanisms, four ask both the writer and the reader to act, one puts the load on the writer alone (a final field), one on the reader alone (join), and two never ask the visibility question at all. The visibility defect of a field without synchronization is not shown by running it; it is modeled as a rule.

The previous lesson worked entirely with setups where the same thread both wrote and read a
value; `start.await()` and `t.join()` only established **order**, and the mechanism providing
visibility was already there without ever being named. This lesson names that mechanism. When
one thread writes a value and another thread reads it, the reader seeing the written value is
**not automatic** — what makes it visible is that a sequencing the language recognizes has been
established between the two actions. This sequencing is called the **happens-before relation**,
and the question this lesson asks the course is: in each of eight distinct publication
mechanisms, **which side** has to do something to establish this relation — only the writer,
only the reader, both, or neither?

The "not automatic" part of the question may look like it contradicts the previous lesson's
examples, where every value was read correctly without any extra effort. The contradiction is
only on the surface: every setup in the previous lesson already used a `CountDownLatch` or
`join`, and these two tools — as will be shown below — are themselves happens-before sources in
their own right. Visibility was not automatic there either; the necessary relation was already
established on its own, and this lesson is naming why that establishment was sufficient.

#### Happens-Before as a Rule

- **CC5** — The happens-before relation is not a timing claim; it does not say **in what order**
  two actions occurred, it only guarantees that once one has occurred, the results of the other
  become **visible**. This relation is never measured anywhere in this lesson; it is only used
  as a rule.

Among the happens-before rules the language recognizes, the ones used in this lesson are these:
actions within a single thread happen-before each other in program order; releasing a monitor
lock happens-before every later action that acquires the same lock; writing to a `volatile`
field happens-before every later action that reads that same field; starting a thread
(`start()`) happens-before every action within that thread; every action within a thread
happens-before another thread successfully returning from a `join()` on that thread; and the
value of a `final` field in the constructor happens-before every thread that reaches that object
by **safe means** after the constructor completes. This list is enough to categorize the eight
mechanisms below.

All of these rules are **transitive**: if action A happens-before B and B happens-before C, then
A also happens-before C — even if the link in between uses a different mechanism. So in a chain
where a value is written in `main`, a thread is `start()`ed, and inside that thread the value is
passed to a second thread with a `CountDownLatch`, the write at the very start reaches the read
at the very end **not in a single jump, but link by link**; each link applies its own rule, and
the chain as a whole is not separately proven. Each of the measurements below isolates **a
single link** of that chain — the direct bond between a writer and a reader.

#### The Writer's Cost: final Fields and Safe Publication

- **CC6** — The measurement places no extra step in the reader's code flow; the reader only
  reads the field. The entire cost is placed on the moment the constructor completes.

```java
// SafePublication.java — the cost of a final field is paid at construction, the reader does nothing extra
public class SafePublication {
    static final class Packet {
        final int value;
        Packet(int value) { this.value = value; }
    }

    public static void main(String[] args) throws InterruptedException {
        Packet box = new Packet(5);
        Thread reader = new Thread(() -> {
            System.out.println("reader adds nothing, just reads Packet.value: " + box.value);
        });
        reader.start();
        reader.join();
    }
}
```

```
reader adds nothing, just reads Packet.value: 5
```

Until the `Packet` constructor completes, the `value` field is considered unwritten; the moment
the constructor finishes, this field is **frozen**. The `reader` thread reading `box.value` does
not take a lock or check a volatile field — it applies no extra discipline at all, because the
discipline it would need was already spent **on the writer's side**, inside the constructor. In
this lesson, the `box` reference reaches `reader` through `start()`, which is already a channel
that establishes a happens-before relation on its own; the `final` field's own guarantee is
broader than that — as long as the constructor does not let `this` escape early, the field's
correct value is seen no matter which channel the reference arrives through. This broader
guarantee is not run separately here: proving it would require **deliberately** publishing the
reference without a happens-before link, and that is exactly the kind of setup the limiting
measurement below avoids.

The guarantee also has a condition: the constructor must **not let `this` escape early**. If a
constructor writes `this` to a static field or registers it with a listener before its own
completion, another thread can see that object while the constructor is **still running** — and
at that moment the `final` field may be unassigned or half-assigned. This lesson does not set up
such an escape; it only records that the `final` field's guarantee is **not unconditional**, and
depends on the constructor's own discipline. `SafePublication` satisfies this condition:
`Packet`'s constructor never writes `this` anywhere, and the constructor finishes right after
`value` is assigned.

#### Eight Mechanisms, Four Categories

- **CC7** — The six mechanisms below are measured with a bounded loop called `waitUntil`: the
  loop condition is tested up to two hundred million times until it becomes true, and the
  duration is never printed anywhere. The only thing measured is whether the condition
  **became true or not**, and the value read when it does.

```java
// Publication.java — is it enough for the writer and reader to use the same mechanism, across six setups
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.BooleanSupplier;

public class Publication {
    static volatile int volatileValue = 0;
    static volatile boolean volatileReady = false;

    static final Object lock = new Object();
    static int lockedValue = 0;
    static boolean lockedReady = false;

    static final AtomicInteger atomicValue = new AtomicInteger(-1);

    static int joinValue = 0;
    static int latchValue = 0;

    static final ThreadLocal<Integer> own = ThreadLocal.withInitial(() -> 0);

    static boolean waitUntil(BooleanSupplier condition) {
        for (long i = 0; i < 200_000_000L; i++) if (condition.getAsBoolean()) return true;
        return false;
    }

    public static void main(String[] args) throws InterruptedException {
        // 1. volatile: the writer writes a volatile field, the reader reads the same volatile field
        Thread t1 = new Thread(() -> { volatileValue = 5; volatileReady = true; });
        t1.start();
        boolean g1 = waitUntil(() -> volatileReady);
        System.out.println("volatile       : seen=" + g1 + " value=" + volatileValue);
        t1.join();

        // 2. synchronized: the writer and reader use the same lock
        Thread t2 = new Thread(() -> { synchronized (lock) { lockedValue = 5; lockedReady = true; } });
        t2.start();
        boolean g2 = waitUntil(() -> { synchronized (lock) { return lockedReady; } });
        int d2; synchronized (lock) { d2 = lockedValue; }
        System.out.println("synchronized   : seen=" + g2 + " value=" + d2);
        t2.join();

        // 3. AtomicInteger: the writer calls set, the reader calls get
        Thread t3 = new Thread(() -> atomicValue.set(5));
        t3.start();
        boolean g3 = waitUntil(() -> atomicValue.get() != -1);
        System.out.println("AtomicInteger  : seen=" + g3 + " value=" + atomicValue.get());
        t3.join();

        // 4. join: the body finishes, the reader reads only after join — plain field, nothing else
        Thread t4 = new Thread(() -> joinValue = 5);
        t4.start();
        t4.join();
        System.out.println("join           : seen=true value=" + joinValue);

        // 5. CountDownLatch: the writer calls countDown, the reader calls await — plain field
        CountDownLatch latch = new CountDownLatch(1);
        Thread t5 = new Thread(() -> { latchValue = 5; latch.countDown(); });
        t5.start();
        latch.await();
        System.out.println("CountDownLatch : seen=true value=" + latchValue);
        t5.join();

        // 6. ThreadLocal: every thread sees its own copy, never the other thread's at all
        Thread t6 = new Thread(() -> own.set(5));
        t6.start();
        t6.join();
        System.out.println("ThreadLocal    : caller's own copy=" + own.get()
                + " (the 5 written by the created thread is not in this copy at all)");
    }
}
```

```
volatile       : seen=true value=5
synchronized   : seen=true value=5
AtomicInteger  : seen=true value=5
join           : seen=true value=5
CountDownLatch : seen=true value=5
ThreadLocal    : caller's own copy=0 (the 5 written by the created thread is not in this copy at all)
```

All six lines read the same way, but their justifications differ. For **`volatile`** and
**`synchronized`**, both the writer and the reader have to touch the same field or the same
lock — if the reader checked a different field or a different lock, no guarantee would remain at
all; these two are written into the **both** column. **`AtomicInteger`** is a third example for
the same reason: the writer calls `set`, the reader calls `get`, both through the same object.
**`CountDownLatch`** is the same again — the writer must call `countDown`, the reader must call
`await`; if either one were missing, the relation would not be established. **`join`** carries a
different load: there is **nothing extra** the writer's side has to do, it only has to finish
normally; the entire load falls on the reader calling `join()` — which is why it is in the
**reader only** column. A `final` field is the opposite: the entire load is on the constructor
completing, i.e. in the **writer only** column. **`ThreadLocal`** never asks the visibility
question at all — since every thread has its own copy, there is **no** expectation that the
writer and reader be visible to each other, which is why it goes in the **neither** column. The
other seven were ways of making a value safe by **sharing** it; `ThreadLocal` instead turns not
sharing itself into the tool — the same-named variable living two separate lives in two threads
does not solve the visibility question, it removes it from the question entirely.

| Category | Mechanisms | Count |
|---|---|---|
| writer only | `final` field | 1 |
| reader only | `join` | 1 |
| both | `volatile`, `synchronized`, `AtomicInteger`, `CountDownLatch` | 4 |
| neither | field without synchronization, `ThreadLocal` | 2 |

**Four** of the eight mechanisms ask both sides to act, **one** puts the load solely on the
writer, **one** solely on the reader, and **two** fall outside the visibility question — one
because no rule establishes it, the other because it is isolated enough that no rule needs to.

Placed side by side, `join` and `CountDownLatch` look at first glance like they do the same job,
but their places in the table differ — one is in **reader only**, the other in **both**. The
distinction comes from this: the happens-before relation `join` establishes is tied to the
thread's **own natural end** — the writer's side has no extra method it calls, the relation is
established however the body finishes. `CountDownLatch` is not tied to such a natural end — the
count can drop either before or after the body finishes, through the writer's side **explicitly**
calling `countDown`. This difference makes `CountDownLatch` more flexible than `join` (a thread
can signal progress before it finishes), but the price of that flexibility is one extra call
placed on the writer's side.

#### Limiting Measurement: Two Modeled Limits

- **CC8** — No code is run in this section. Both claims are supported by reference to the rule
  and by citing earlier courses, not by measurement — because measuring it would either be
  misleading or impossible by definition.

The first item in the table's `neither` row, an ordinary field without synchronization, is
deliberately not run. This is not from laziness, it is a limit: a thread writing an
unsynchronized field and another thread reading it in a loop can, even on the same program on
the same machine, sometimes show the visibility right away and sometimes not — the result
depends on whatever optimization decision the compiler happens to make at that moment, and this
lesson does not measure such a decision, because that is not M08/K04's own measurement axis.
Running such a loop here and writing "it did not show up" or "it showed up" would be presenting a
one-time observation as a general rule; but the rule is already established above, and **no
item covers this field.** The correct reading is: if a field does not fall under one of the
seven mechanisms in this list, there is no guarantee at all about that field's visibility —
neither "visible" nor "invisible" is guaranteed, only **not guaranteed.**

This means a short-lived trial could **always** show visibility and mislead the programmer. The
reason the `waitUntil` loop in `Publication` can remain a genuine test is that all three fields
it checks (`volatileReady`, `lockedReady`, `atomicValue`) are covered by a rule; if the same loop
checked an unsynchronized `boolean` field, it would probably still print `seen=true` — because in
a short-lived program, the compiler may not yet have had the opportunity to reorder the loop.
This apparent success is not proof; it only means that in that particular run, no price was
paid for breaking the rule. The same decision measured in the Just-in-Time Compilation lesson
applies here too: the same source, unchanged, can behave differently depending on a runtime
decision — the difference is that lesson measured the decision's instruction count, this lesson
only points out that the decision can break **observability**.

The second limit concerns the `volatile` field, and it is the one most readily confused with the
visibility guarantee being measured. What `volatile` gives is **visibility and ordering**, not
**atomicity** itself. The read-increment-write trio was established as a concept in the M01/K05
Operating System Concepts course; the same trio can also be split on a `volatile` field — when
two threads both write `counter++` to the same `volatile` counter, even though both sides do see
the operation, one can perform its own increment before it has read the other's increment, and
an increment gets lost. This lesson does not **run** a real race between these two sides,
because the result would be environment-dependent, and how many times the lost increment
happens is not tied anywhere to this lesson's measurement axis. The claim remains a rule: a
`volatile` gives visibility for a single read or a single write; it gives nothing for an
operation that combines a read and a write. The actual tool for that is
**compare-and-set**, on `AtomicInteger`, measured in the next lesson.

#### Summary

- Visibility is not automatic; a write becoming visible to a read depends on a happens-before
  relation the language recognizes having been established between the two.
- Of eight publication mechanisms, **four** (`volatile`, `synchronized`, `AtomicInteger`,
  `CountDownLatch`) ask both the writer and the reader to act, **one** (a `final` field) puts
  the load solely on the writer, **one** (`join`) puts the load solely on the reader, and
  **two** (a field without synchronization, `ThreadLocal`) fall outside the visibility question.
- The cost of a `final` field is paid at construction: once the constructor completes, the
  reader sees the correct value without applying any extra discipline.
- The visibility defect of a field without synchronization is not shown by running it; this
  lesson deliberately models it, because the result would be environment-dependent.
- `volatile` gives visibility and ordering, not atomicity; the read-increment-write trio can
  still be split on a `volatile` field, and this lesson explains that as a rule, not by running
  it.

#### Next Step

In this lesson, the `synchronized` block entered the table as just one row, and it was said
that the reader and the writer need to use the **same** lock — but the phrase "same lock" was
never questioned itself; `lock` here was a single static field, and the two threads using the
same object was given from the start. The next lesson removes that assumption and measures who
a monitor lock belongs to: when the same synchronized method is called on two separate
instances, how many separate locks come into play, and how many times is it counted when a
thread re-enters a lock it already holds?

### Synchronization

Source: https://academia.sh/en/courses/jvm-and-concurrency/synchronization

Re-entering a monitor lock raises the hold count from 0 to 2 and then brings it back down. The same synchronized method called on the same instance uses a single lock; called on two separate instances, or on an instance method and a class method, two separate locks come into play — in two of the three setups, the lock count rises to two.

The previous lesson put the `synchronized` block into the table as a single row and said that
the reader and the writer must use the **same** lock — but in that lesson, `lock` was a single
static field, and the two threads sharing the same object was given from the start. This lesson
removes that assumption. The `synchronized` keyword does not explicitly say in the source which
lock will be used; the lock used when an instance method is called depends on **the object that
call was made through**, and the same source line can correspond to two different locks
depending on which object it was called on.

This ambiguity requires separating two distinct questions. The first: is a thread currently
holding a lock, and if so, how many times? This is a question about a single thread's own
state, and the first section of this lesson measures it. The second: do two separate calls use
the **same** lock? This is a question that can be answered not by looking at the source text but
by looking at which object the two calls were made through, and the second section of this
lesson counts it across three setups.

#### Who Holds the Monitor Lock

- **CC9** — `Thread.holdsLock(object)` only asks whether the calling thread currently holds
  that object's lock; it does not ask whether some other thread holds it — that information is
  never offered by the API at all. **How many times** a lock is held is measured with
  `ReentrantLock.getHoldCount()` instead — the monitor lock itself does not expose this count,
  which is why the measurement is done with an explicit lock.

```java
// LockHolding.java — who holds the monitor lock is measured with holdsLock, re-entry depth with ReentrantLock
import java.util.concurrent.locks.ReentrantLock;

public class LockHolding {
    static final Object monitor = new Object();
    static final ReentrantLock explicitLock = new ReentrantLock();

    public static void main(String[] args) {
        System.out.println("is lock held (before start)   : " + Thread.holdsLock(monitor));
        synchronized (monitor) {
            System.out.println("is lock held (inside block)    : " + Thread.holdsLock(monitor));
        }
        System.out.println("is lock held (after block)     : " + Thread.holdsLock(monitor));

        System.out.println();
        System.out.println("before start        holdCount=" + explicitLock.getHoldCount());
        explicitLock.lock();
        try {
            System.out.println("after 1st entry     holdCount=" + explicitLock.getHoldCount());
            explicitLock.lock(); // the same thread re-enters the same lock
            try {
                System.out.println("after 2nd entry     holdCount=" + explicitLock.getHoldCount());
            } finally {
                explicitLock.unlock();
                System.out.println("after inner exit    holdCount=" + explicitLock.getHoldCount());
            }
        } finally {
            explicitLock.unlock();
            System.out.println("after outer exit    holdCount=" + explicitLock.getHoldCount());
        }
    }
}
```

```
is lock held (before start)   : false
is lock held (inside block)    : true
is lock held (after block)     : false

before start        holdCount=0
after 1st entry     holdCount=1
after 2nd entry     holdCount=2
after inner exit    holdCount=1
after outer exit    holdCount=0
```

The first three lines measure the lock's **identity**: `holdsLock` becomes `true` the moment
the `synchronized (monitor)` block is entered, and drops back to `false` the moment the block is
left — holding the lock itself is tied to block boundaries and requires no separate release
call. The five lines below measure **re-entrancy**. `ReentrantLock` gets its name from exactly
this: when the same thread enters a lock it already holds for a second time, it is not blocked,
and the hold count rises from `1` to `2`. When the inner block is exited, the lock is **still
held** — the count drops to `1`, not zero — and only reaches `0` once the outer block is also
exited. This is a property the monitor lock (built-in `synchronized`) also carries but does not
expose as a number: when a method calls another synchronized method on the same object and
re-enters the same lock, it does not deadlock, because the lock belongs to the **thread**, not
the call — the same thread can request the lock it already holds again.

Without re-entrancy, a synchronized method calling another synchronized method on its own class
would mean **locking itself out** — a thread would wait forever because it requested a lock it
already owned, a second time. This is the smallest possible form of deadlock that can occur
within a single thread, and the existence of the hold count is exactly what prevents it: the
framework asks "is this lock already mine" on every `lock()` call, and if the answer is yes, it
does not block, it increments the count and moves on. `holdsLock` itself does not expose this
count, because the question it asks is different — it only asks "am I holding it," not "how many
times am I holding it"; both questions are answered by separate tools in this lesson.

#### The Lock Belongs to the Object, Not the Code

- **CC10** — All three setups use the same two-line log pattern: a thread prints `"X entered"`
  when it enters, `"X left"` when it leaves. The **order** of the log proves which lock is
  shared — no duration is measured anywhere.

```java
// Box.java — the lock belongs to the object, not the code: the sync method uses the instance lock, the static one the class lock
import java.util.List;
import java.util.concurrent.CountDownLatch;

public class Box {
    synchronized void enter(List<String> log, String label, CountDownLatch entered, CountDownLatch proceed)
            throws InterruptedException {
        log.add(label + " entered");
        if (entered != null) entered.countDown();
        if (proceed != null) proceed.await();
        log.add(label + " left");
    }

    static synchronized void classEnter(List<String> log, String label) {
        log.add(label + " entered (class lock)");
        log.add(label + " left (class lock)");
    }
}
```

`enter` can optionally stop the thread that calls it: if `entered` is given, it signals the
moment it enters; if `proceed` is given, it holds the lock until it is released. The three
setups use these two knobs differently.

```java
// HowManyLocks.java — how many separate locks come into play across three setups
import java.util.*;
import java.util.concurrent.CountDownLatch;

public class HowManyLocks {
    static List<String> setup1SameInstance() throws InterruptedException {
        Box box = new Box();
        List<String> log = Collections.synchronizedList(new ArrayList<>());
        CountDownLatch enteredA = new CountDownLatch(1);
        CountDownLatch releaseA = new CountDownLatch(1);

        Thread a = new Thread(() -> { try { box.enter(log, "A", enteredA, releaseA); } catch (InterruptedException e) { } });
        a.start();
        enteredA.await(); // A has taken the lock and holds it

        Thread b = new Thread(() -> { try { box.enter(log, "B", null, null); } catch (InterruptedException e) { } });
        b.start(); // B wants the same lock, cannot enter until A releases it

        releaseA.countDown(); // release A
        a.join();
        b.join();
        return log;
    }

    static List<String> setup2DifferentInstance() throws InterruptedException {
        Box box1 = new Box();
        Box box2 = new Box();
        List<String> log = Collections.synchronizedList(new ArrayList<>());
        CountDownLatch enteredA = new CountDownLatch(1);
        CountDownLatch releaseA = new CountDownLatch(1);
        CountDownLatch doneB = new CountDownLatch(1);

        Thread a = new Thread(() -> { try { box1.enter(log, "A", enteredA, releaseA); } catch (InterruptedException e) { } });
        a.start();
        enteredA.await(); // A holds box1's lock

        Thread b = new Thread(() -> {
            try { box2.enter(log, "B", null, null); doneB.countDown(); }
            catch (InterruptedException e) { }
        });
        b.start();
        doneB.await(); // B finished while A is still waiting -- meaning it used a separate lock

        releaseA.countDown();
        a.join();
        b.join();
        return log;
    }

    static List<String> setup3InstanceAndClass() throws InterruptedException {
        Box box = new Box();
        List<String> log = Collections.synchronizedList(new ArrayList<>());
        CountDownLatch enteredA = new CountDownLatch(1);
        CountDownLatch releaseA = new CountDownLatch(1);
        CountDownLatch doneB = new CountDownLatch(1);

        Thread a = new Thread(() -> { try { box.enter(log, "A", enteredA, releaseA); } catch (InterruptedException e) { } });
        a.start();
        enteredA.await(); // A holds the instance lock

        Thread b = new Thread(() -> { Box.classEnter(log, "B"); doneB.countDown(); });
        b.start();
        doneB.await(); // B finished while A is still waiting -- the class lock is separate

        releaseA.countDown();
        a.join();
        b.join();
        return log;
    }

    public static void main(String[] args) throws InterruptedException {
        System.out.println("1. two calls on the same instance:");
        System.out.println("   " + setup1SameInstance());
        System.out.println("2. two calls on two separate instances:");
        System.out.println("   " + setup2DifferentInstance());
        System.out.println("3. an instance method and a class method:");
        System.out.println("   " + setup3InstanceAndClass());
    }
}
```

```
1. two calls on the same instance:
   [A entered, A left, B entered, B left]
2. two calls on two separate instances:
   [A entered, B entered, B left, A left]
3. an instance method and a class method:
   [A entered, B entered (class lock), B left (class lock), A left]
```

In all three setups, A does not release the lock until `releaseA.countDown()` is called. In the
first setup, B's log **only starts after A has fully left** — B never even reaches the first line
of the `enter` method, because entering a synchronized method's body first requires acquiring
the same lock, and that lock is with A. In the second and third setups, both B's **entry and
exit** land in the log while A still has not released the lock — B's completion never waits for
A's release at all. This is direct proof that two separate locks are in play: if the same lock
were shared, B would also have to wait for A's exit, as in the first setup.

The strength of the proof lies exactly here: no line claims "there are two separate locks," only
an ordering is observed, and that ordering has no other explanation. B being able to call
`doneB.countDown()` means it has run the `enter` or `classEnter` method's body **all the way
through**; that in turn means the synchronized block that method entered has both acquired and
released its lock. A, meanwhile, is still holding its own lock at the same moment — `releaseA`
has not dropped yet. No deadlock occurs in the third setup even though both threads hold a lock
at the same time, because the two locks belong to separate objects; in the first setup, since
B's `entered` parameter is `null`, there is no independent signal for exactly when B enters the
lock — but the order in the log already leaves only one possibility.

| Setup | Calls | Number of separate locks |
|---|---|---|
| 1. same instance | `box.enter` × 2 | 1 |
| 2. two separate instances | `box1.enter`, `box2.enter` | 2 |
| 3. instance + class method | `box.enter`, `Box.classEnter` | 2 |

In **two of the three** setups, the number of separate locks rises to two. An instance lock is
tied to the object it is called on, while a class lock is tied to a single `Class` object and is
shared across all instances — but it is **not the same** lock as an instance lock. If a class
has both an instance and a static synchronized method, one thread can be inside the instance
method at the same time another thread calls the static method; neither one blocks the other at
all.

This distinction shows that which object is chosen as the lock in `synchronized` is a **design
decision**. `Box.enter` implicitly locks on `this` — this is the default behavior for a class's
synchronized methods. If that same lock is a reference accessible from outside the class (if
`this` is part of a public API), a caller the class knows nothing about could also lay claim to
the same lock by writing `synchronized (box) { ... }`, creating unexpected contention from the
outside. `Box.enter` uses `this` for this lesson's measurement, but the third setup already shows
the reverse as well: just as an instance lock and a class lock are independent of each other, a
class can also use its own private lock object — in that case no caller from outside can reach
that lock with `synchronized`, because the reference itself never leaves.

#### Limiting Measurement: A Lock Gives No Order

- **CC11** — In this section, **which order** waiting threads will enter the lock is not
  measured by running it; if it were measured, the result would depend on the scheduler, and
  that would mean running a race condition the course's rules forbid.

In all three setups above, there were only two threads, and which one would wait was known from
the start. When a **third or fourth** thread requesting the same lock is added, which of the
waiters gets in once the lock frees up is not specified by `synchronized` itself — it guarantees
mutual exclusion, not order. The **fair lock** option requested by passing `true` to
`ReentrantLock`'s constructor aims to approximate waiting order, but this lesson does not measure
that order from a run; the option's existence and purpose are only **modeled** here. Many
threads requesting the same lock waiting on each other indefinitely is a separate defect, and it
was already established as a concept in the M01/K05 Operating System Concepts course; this
lesson does not re-establish it, it only reminds that a lock does **not** give order, by leaving
that order outside the measurement.

Why the fair lock is not the default is also a modeled justification, not a measured number:
forcing order requires checking the head of the waiting queue on every `lock()` call, and this
check runs **every time**, not only when there is a waiter. The non-fair form of `synchronized`
and `ReentrantLock` lets a new call arriving at an idle lock enter directly without ever polling
the queue. This lesson does not measure the cost difference between these two options, because
measuring it would mean doing a real version comparison; the only thing stated is that order is
an **optional** guarantee, and the default does not give it. As the number of threads waiting on
a lock grows, which design gets chosen is not this lesson's question — this lesson only fixes
what guarantee is **absent** as long as no choice is made.

The class lock in the third setup is also singular within itself: **all** static synchronized
methods of the `Box` class share the same `Class` object's lock, just as all instance
synchronized methods of an instance share that instance's lock. If a class's two separate static
synchronized methods were called from two separate threads at the same time, this setup would
put them in a queue just like the first setup did — that was not measured here, because this
lesson's question was already whether the instance lock and the class lock are **separate** from
each other, not whether the class lock is singular within itself.

#### Summary

- `Thread.holdsLock` deterministically states whether a lock is currently held by the caller;
  `ReentrantLock.getHoldCount()` counts re-entry depth, rising from `0` to `2` and back to `0`
  on two nested entries.
- A lock belongs to a thread, not a call: the same thread is not blocked when it re-enters a
  lock it already holds.
- An instance lock is tied to the **object** it is called on: the same synchronized method
  called on two separate instances uses two separate locks; a class lock is a **separate,
  third** lock apart from these.
- Only in one of the three setups (two calls on the same instance) is a single lock shared;
  in the other two, the number of separate locks rises to two.
- A lock provides mutual exclusion and visibility, not order among waiters; if order is
  wanted, the fair-lock option is modeled, not measured from a run.

#### Next Step

In all three of these setups, taking and releasing the lock by hand was the caller's job — a
`synchronized` block or an `explicitLock.lock()`/`unlock()` pair was written by hand each time,
and guaranteeing that the lock was released fell to a `finally` block every time. Every one of
`HowManyLocks`'s three setups repeated this pattern; in a real program, this repetition can turn
into a defect where a forgotten `unlock()` call holds a lock forever. The next lesson hands this
manual holding off to ready-made types in the standard library: whether a counter manages its
own lock, when a latch releases a waiter, who pays when a list is written to — the lock no
longer appears in the source text, but the work still falls on someone; the next question is
which side that load falls on.

### Concurrency Utilities

Source: https://academia.sh/en/courses/jvm-and-concurrency/concurrency-utilities

Compare-and-set reaches its result in three attempts under a modeled contention and never leaves the caller for the whole operation. CountDownLatch and Semaphore block the waiter and have the other side drop the count; CopyOnWriteArrayList keeps each of four snapshots unchanged while the writer produces a new array on every change.

The previous lesson talked about taking and releasing a lock by hand — a `synchronized` block
or a `lock()`/`unlock()` pair was rewritten on every call, and guaranteeing the release fell to
`finally`. The standard library's concurrency package removes this manual holding from the
source text: `AtomicInteger` provides an atomic update without any lock at all,
`CountDownLatch` and `Semaphore` split waiting and releasing between two separate sides,
`CopyOnWriteArrayList` and `ConcurrentHashMap` carry their own discipline against concurrent
modification during traversal. The source text no longer has a single `synchronized` keyword —
but the work itself still falls on someone. This lesson's question: which side does the utility
type load that work onto?

Four utilities give four different answers. `AtomicInteger` does not distribute the work at
all, it keeps it entirely in the caller's own loop. `CountDownLatch` and `Semaphore` split the
work between two sides: one waits, the other releases, and these two roles never merge into the
same call. `CopyOnWriteArrayList` loads the cost onto the **writing** side and exempts the
traversing side entirely. `ConcurrentHashMap` lets neither side block, allowing both to run at
the same time. The common point is that none of them carries a lock keyword in the source text;
the difference is where the mechanism that replaces the lock places the work.

#### Compare-and-Set: The Work Stays with the Caller

- **CC12** — No real contention is run; on the first two attempts, the counter is deliberately
  shifted as if "another side stepped in." This modeled setup runs within a single thread,
  deterministically.

`AtomicInteger` contains no lock at all, but provides an atomic update with a single method:
**compare-and-set**. This method says "if the value is this, change it to that; if not, do
nothing and say it failed"; when the caller receives a failure response, it reads the new value
and tries again. The entire loop stays **in the caller's own** code flow — no new thread is
created, no lock comes into play.

```java
// CompareAndSet.java — the work stays with the caller, a contention is modeled so the retry count is deterministic
import java.util.concurrent.atomic.AtomicInteger;

public class CompareAndSet {
    public static void main(String[] args) {
        AtomicInteger counter = new AtomicInteger(0);
        int attemptCount = 0;
        int oldValue, newValue;
        do {
            oldValue = counter.get();
            newValue = oldValue + 1;
            attemptCount++;
            // on the first two attempts, a modeled contention: shift the value as if another side stepped in
            if (attemptCount <= 2) counter.incrementAndGet();
        } while (!counter.compareAndSet(oldValue, newValue));
        System.out.println("attempt count seen by the caller: " + attemptCount);
        System.out.println("counter's final value            : " + counter.get());
    }
}
```

```
attempt count seen by the caller: 3
counter's final value            : 3
```

Every turn of the loop first reads the counter (`oldValue`), computes the target value
(`newValue`), then calls `compareAndSet(oldValue, newValue)`. A deliberate "shift" is inserted
on the first two turns — in a real program, this shift would be caused by another thread's own
`compareAndSet`; this lesson never creates such a thread, it writes the shift directly and
**records that it is modeling** it right here. This is why the first two calls fail — the
`oldValue` the caller read is no longer valid by the time `compareAndSet` runs — and on the
third call nothing steps in and the operation reaches its result. Across the three attempts, the
work stayed in the same thread the whole time; no lock was taken, no other party was called,
only the read-compute-try loop was repeated.

The difference between this loop and a `synchronized` block lies in how it responds to failure.
When a lock request is denied, the caller is **blocked** and can do nothing until the lock frees
up; when `compareAndSet` is denied, the caller is not blocked, it just receives a boolean
`false` and can respond however it likes — retrying, as here, or giving up and moving to
something else if it wants. There is no upper bound on the number of retries: the more frequent
the contention, the more frequent the retries, but on every attempt the work stays in the
caller's own flow. In a real multi-threaded workload, this number would be environment-dependent;
here it is `3`, fixed and repeatable, because it was modeled.

#### The Waiter Is Blocked, the Other Side Drops the Count

- **CC13** — The log's order carries a single proof: the line where B gets the permit **can
  never appear before** the line where A releases it — because `Semaphore` gives out a single
  permit, and B is blocked inside `acquire()` until A releases it.

`CountDownLatch` blocks the waiter until the count reaches zero; dropping the count is
**always** the other side's job — the waiter itself never drops its own count. `Semaphore`
carries a contract close to this but different: it hands out a fixed number of **permits**,
`acquire()` blocks when none are left, `release()` gives a permit back. The setup below uses both
together: `enteredA` and `doneB` are each a `CountDownLatch`, while the mutual exclusion between
them is provided by a single-permit `Semaphore`.

```java
// Sentry.java — CountDownLatch blocks the waiter, Semaphore has the other side release the permit
import java.util.*;
import java.util.concurrent.*;

public class Sentry {
    public static void main(String[] args) throws InterruptedException {
        List<String> log = Collections.synchronizedList(new ArrayList<>());
        Semaphore permit = new Semaphore(1);
        CountDownLatch enteredA = new CountDownLatch(1);
        CountDownLatch release = new CountDownLatch(1);
        CountDownLatch doneB = new CountDownLatch(1);

        Thread a = new Thread(() -> {
            try {
                permit.acquire();
                log.add("A got the permit");
                enteredA.countDown();
                release.await();
                log.add("A is releasing the permit");
                permit.release();
            } catch (InterruptedException e) { }
        });
        a.start();
        enteredA.await();

        Thread b = new Thread(() -> {
            try {
                permit.acquire(); // waits until A releases it
                log.add("B got the permit");
                permit.release();
                doneB.countDown();
            } catch (InterruptedException e) { }
        });
        b.start();

        release.countDown();
        doneB.await();
        a.join(); b.join();
        System.out.println(log);
    }
}
```

```
[A got the permit, A is releasing the permit, B got the permit]
```

After A takes the single permit, it signals with `enteredA` and waits on its own `release`
latch. B is started during this time and calls `permit.acquire()` — but the permit is with A, so
B's `"B got the permit"` line cannot land in the log, blocked exactly like the second lock
requester from the previous lesson. When the main thread calls `release.countDown()`, A wakes
up, releases the permit (`"A is releasing the permit"`), and only after that moment can B get the
permit. The waiting side (B) cannot release itself; the one that drops the count and the one
that releases the permit is **always the other** side.

It is worth noting that three separate synchronization tools each do a separate job in the same
program here. `enteredA` and `doneB` each carry a **one-time** signal — each drops once and is
never used again. `permit`, by contrast, is **reusable**: when A releases it, B takes it, and if
a third caller arrived after B released it, that caller could take it too. `CountDownLatch`
answers the question "wait for something that happens once," while `Semaphore` answers "limit
how many parties can be inside at the same time" — here the limit is set to one, so `Semaphore`
looks a lot like a lock, but if the permit count were more than one, it would let more than one
caller in at the same time — a flexibility `synchronized` could never offer.

#### Copy-on-Write: The Traverser Pays Nothing, the Writer Does

- **CC14** — Four snapshots are taken interspersed within the sequence of additions: the first
  three **before** the addition that follows them, the fourth **after** all three additions.
  Each snapshot sees the size it had at the moment it was taken, and that size is never affected
  by later additions.

`CopyOnWriteArrayList` leaves the current array untouched on a write operation (`add`, `remove`,
`set`) and produces a **new** array, then points its internal reference to that new array. This
is why an iterator taken before a write freezes at the array it took — later writes have no
effect on that iterator at all, and do not throw an exception on top of it either.

```java
// CopyOnWrite.java — the reader pays nothing, the writer produces a new array on every change
import java.util.*;
import java.util.concurrent.CopyOnWriteArrayList;

public class CopyOnWrite {
    public static void main(String[] args) {
        CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>();
        List<Iterator<String>> snapshots = new ArrayList<>();

        snapshots.add(list.iterator());
        list.add("a");
        snapshots.add(list.iterator());
        list.add("b");
        snapshots.add(list.iterator());
        list.add("c");
        snapshots.add(list.iterator());

        int i = 0;
        for (Iterator<String> it : snapshots) {
            int count = 0;
            while (it.hasNext()) { it.next(); count++; }
            System.out.println("snapshot " + i + " element count seen: " + count);
            i++;
        }
        System.out.println("final list size: " + list.size());
    }
}
```

```
snapshot 0 element count seen: 0
snapshot 1 element count seen: 1
snapshot 2 element count seen: 2
snapshot 3 element count seen: 3
final list size: 3
```

The first three iterators are taken **before** the `list.add` call that follows them, the
fourth after all three additions; all of them are traversed and read **after** the loop —
by the time the third addition happens, the first three iterators already exist. Even so, none
of them sees the later additions: snapshot zero always says `0`, snapshot one always says `1`.
Having observed four different sizes is indirect proof that four separate arrays existed —
`the traversing side pays nothing` because it never shares the array it took, `the writer pays`
because every `add` call copies, **from scratch**, an array that keeps growing up to the final
value of `list.size()`. Three `add` calls produce three new arrays; counting the first, empty
array too, the number of distinct sizes observed over the course of the measurement is **four**.

Where the cost lands also determines when this structure is chosen. If traversal is frequent and
writes are rare (for example, a listener list that is traversed constantly but almost never
changes), paying a copying cost per write is acceptable, because the paying side runs rarely
anyway. If writes are frequent and traversal is rare, the same cost turns into a full copy every
single time, and that may no longer be acceptable — in that case, the pattern measured below for
`ConcurrentHashMap`, where neither side pays for a full copy, fits better. This lesson does not
run a speed comparison between the two structures, it only counts which side pays what; the
choice is a design decision that looks at which side that payment is cheaper on.

#### Who Writes in Concurrent Collections

- **CC15** — The write during traversal happens on a separate thread and is completed with
  `join()`; the order is not left to the scheduler, exactly when the write happens is chosen
  explicitly inside the loop.

`ConcurrentHashMap`'s weakly consistent iterator and `CopyOnWriteArrayList`'s traversal over a
snapshot were already established in the M08/K03 Standard Library and Streams course; this
lesson does not reintroduce those classes, it only adds its own question: when a write happens
during traversal, **which thread** does it?

```java
// SharedMap.java — a write during traversal throws no exception, the writing thread is labeled clearly
import java.util.concurrent.ConcurrentHashMap;

public class SharedMap {
    public static void main(String[] args) throws InterruptedException {
        ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>();
        map.put("a", 1);
        map.put("b", 2);

        int seen = 0;
        for (var entry : map.entrySet()) {
            seen++;
            if (seen == 1) {
                Thread writer = new Thread(() -> {
                    map.put("c", 3);
                    System.out.println("writer: created thread");
                });
                writer.start();
                writer.join();
            }
        }
        System.out.println("traversal finished without exception, entries visited=" + seen);
        System.out.println("size after traversal: " + map.size());
    }
}
```

```
writer: created thread
traversal finished without exception, entries visited=3
size after traversal: 3
```

The write runs on a **created** thread, while traversal is still ongoing (at `seen == 1`, while
the loop is still on its very first entry); `join()` guarantees that this write completes before
traversal continues. Traversal finishes without throwing any exception — `ArrayList`'s
fail-fast iterator would throw `ConcurrentModificationException` in the same setup; that behavior
was already measured in the previous course. The third entry added in this run does show up in
the traversal too, but that is not something `ConcurrentHashMap` **guarantees** — the iterator
may show a view from either before or after the moment of the write, both are consistent with
the contract; the only thing guaranteed is that no exception is thrown.

The line `writer: created thread` answers this lesson's own added question. The behavior
measured in the previous course held even within a single thread — if the **same** thread that
took an iterator modified the structure afterward, that too would throw a fail-fast exception.
Here, the party doing the modifying is a separate thread, and as the label shows, it is a
**created** thread, not the traversing `main` itself. `ConcurrentHashMap`'s contract never asks
which thread is writing — it does not throw regardless of who writes; the identity is added here
only for tracking purposes, it is not part of the contract.

#### Limiting Measurement: A Single Variable's Atomicity

In this section, no new code is run. `AtomicInteger` gives atomicity to a **single** variable;
any operation that reads and changes that variable appears as an indivisible whole. But if a
program wants to update **two** atomic variables together while keeping a relationship between
them (for example, always keeping a total equal to a counter), that relationship falls outside
the guarantee `AtomicInteger` gives — each variable is indivisible on its own, the two together
are not. This is another view of the read-increment-write concept established in the M01/K05
Operating System Concepts course, and it is likewise explained here **by modeling it, not by
running it**: a moment where a thread steps in between two atomic variables and updates only one
of them is always possible, and showing whether that moment actually occurs from a run would
produce an environment-dependent result.

The same limit holds for `CopyOnWriteArrayList` and `ConcurrentHashMap`: each of them maintains
**its own** internal consistency — an `add` call never leaves behind a half-finished array, a
`put` call never shows the map in an inconsistent intermediate state — but this does not
guarantee a larger rule formed **together** by multiple operations on the same collection. Code
that first adds an element to a list and then writes a second field tied to that element into
another map takes on, itself, the responsibility of keeping two separate concurrent collections
consistent together; the utility types only guarantee the integrity within their own boundaries.

#### Summary

- `AtomicInteger`'s compare-and-set loop stays entirely in the caller's code flow; no new
  thread or lock comes into play, only the read-compute-try cycle repeats.
- In `CountDownLatch` and `Semaphore`, the waiting side cannot release itself; the one that
  drops the count or releases the permit is always the **other** side.
- In `CopyOnWriteArrayList`, the traversing side pays no cost at all, because it never shares
  the array it took; the writer produces a new array on every change.
- `ConcurrentHashMap` throws no exception on a write during traversal; which thread does the
  writing is known from the program's own setup, but whether the new entry shows up in the
  traversal is not guaranteed.
- Keeping two atomic variables consistent together falls outside `AtomicInteger`'s guarantee;
  this limit is explained by rule, not by running it.

#### Next Step

All four of these utility types took over the job of holding a lock by hand — `CompareAndSet`
used no lock at all, `Sentry` freed the permit and the latch from being taken by hand,
`CopyOnWrite` and `SharedMap` provided consistency without a lock. But the caller still chooses
**who will run** the task: in `Sentry`, both A and B, and in `SharedMap`, the writer too, were
all set up by hand with `new Thread` and started by calling `start()` — that was still the
caller itself. The next topic hands this choice off to a service and asks it the same question:
when a task is given to a pool, does the pool's own thread run it, or the party handing off the
task — and how does this choice change with the pool's current load?

### Executor Services

Source: https://academia.sh/en/courses/jvm-and-concurrency/executor-services

The same execute call runs on its own thread when the pool is idle and on the calling thread when it is full; distinct thread identity count in a fixed pool stays bounded by the core count, and the rejection policy is a parameter chosen by the caller. An exception thrown via execute reaches the uncaught exception handler, while one thrown via submit only reaches the caller once get is called.

The previous lesson measured how much of the work concurrency utilities keep with the caller and
how much they hand off elsewhere: with `AtomicInteger` the retry stayed with the caller, with
`CountDownLatch` and `Semaphore` the waiter was blocked and the count was dropped by another
side — but the caller itself still decided **which thread** the task would run on, by choosing
whether to call it with `run()` or `start()`. This lesson hands that choice off to a service. An
executor service takes a task and decides for itself who will run it; the question is the same —
which thread runs this step — but the answer is no longer in the call line, it is in the
executor's state at that moment.

The course's sharpest example sits right here, because the code for the two states is
**identical**. The same `execute` call is written whether the pool is idle or full; the only
thing that makes the difference is something that never appears at the call site at all — the
pool's fullness at that moment.

An executor service is built from three parts: a bounded number of threads, a queue holding
waiting tasks, and a rejection policy that kicks in once the queue is also full. In the previous
lesson, the difference between `run()` and `start()` was a single method name; here the
difference shrinks even further — the same method name, the same signature, the same call line,
but two different results. These three sections measure those three parts in turn: how many
distinct threads actually run, who the task falls to when the pool is full, and which side
manages the moment of fullness.

#### Who Runs the Task: Distinct Identity Count

`ExecutorService` is an abstraction that takes a list of tasks and decides for itself which
thread will run each one. The caller writes the task as a `Callable` or `Runnable`; the
executor's own rule determines who runs it. To see this rule as a number, distinct thread
identity is measured: each task adds the thread it ran on to a shared set.

**CC16.** Distinct thread identity is counted with a set; each item added to the set is
distinguished only by object identity (`==` equivalence), no name or number is ever printed.
**CC17.** A fixed-size executor creates a new thread rather than reusing an existing idle one
until it reaches the core thread count; this rule belongs to the executor itself, not to the
scheduler.
**CC18.** When the core and maximum thread counts are given as equal, the pool never exceeds
that number; tasks entering the queue are processed in order by the existing threads.

```java
// Identities.java — a set that counts distinct thread identity using == equivalence (helper source, no main)
class Identities {
    static java.util.Set<Thread> newSet() {
        return java.util.Collections.synchronizedSet(
                java.util.Collections.newSetFromMap(new java.util.IdentityHashMap<>()));
    }
}
```

```java
// DistinctIdentityCount.java — even as the task count grows in a fixed-size pool, distinct identity count stays fixed
import java.util.*;
import java.util.concurrent.*;

public class DistinctIdentityCount {
    static int distinctIdentities(ExecutorService executor, int taskCount) throws Exception {
        Set<Thread> seen = Identities.newSet();
        List<Callable<Void>> tasks = new ArrayList<>();
        for (int i = 0; i < taskCount; i++) {
            tasks.add(() -> { seen.add(Thread.currentThread()); return null; });
        }
        executor.invokeAll(tasks);
        return seen.size();
    }

    public static void main(String[] args) throws Exception {
        try (ExecutorService fourPool1 = Executors.newFixedThreadPool(4)) {
            System.out.println("4 tasks, fixed pool(4)  -> distinct identities: " + distinctIdentities(fourPool1, 4));
        }
        try (ExecutorService fourPool2 = Executors.newFixedThreadPool(4)) {
            System.out.println("8 tasks, fixed pool(4)  -> distinct identities: " + distinctIdentities(fourPool2, 8));
        }
        try (ExecutorService onePool = Executors.newFixedThreadPool(1)) {
            System.out.println("4 tasks, fixed pool(1)  -> distinct identities: " + distinctIdentities(onePool, 4));
        }
    }
}
```

```
4 tasks, fixed pool(4)  -> distinct identities: 4
8 tasks, fixed pool(4)  -> distinct identities: 4
4 tasks, fixed pool(1)  -> distinct identities: 1
```

The measurement uses the `invokeAll` method rather than calling `submit` one by one and waiting
on the leftover futures — because `invokeAll` does not return until every given task has
finished. This is the way to safely compare all three setups without leaving the order to the
scheduler: once the set's final size is read, it is certain that every task in that setup
actually ran and added its own identity to the set.

The `ExecutorService` inside the `try` block's parentheses joins the try-with-resources pattern
established in the Standard Library and Streams course through an `AutoCloseable`
implementation; the closing call here too is implicit and automatic once the block ends. Three
lines ask the same question across three separate setups: four tasks in a pool sized four
produce four distinct identities — each task finds its own thread (CC17). Eight tasks in a pool
of the same size produce, **again**, four distinct identities; the remaining four tasks do not
spawn a new thread, they fall to one of the existing four (CC18). In a single-thread pool, four
tasks fall into a single identity — pool size is an upper bound independent of how many tasks
arrive. The number stays fixed because the rule belongs not to the scheduler but to the
executor's own constructor: `newFixedThreadPool(4)` sets this bound by giving both the core and
maximum thread count as `4`.

#### While the Pool Is Full, the Giving Side Runs It

To ask the same question while the pool is full, a bounded queue and a single-thread pool are
set up. The first task is deliberately held on a `CountDownLatch`; this is the way to keep the
pool's one thread busy and **run-time model** the queue's fullness — the order is not left to
the scheduler.

```java
// PoolSaturation.java — the same execute call, the difference is decided by the pool's current fullness
import java.util.concurrent.*;

public class PoolSaturation {
    static String who(Thread caller) {
        return Thread.currentThread() == caller ? "caller" : "created";
    }

    public static void main(String[] args) throws Exception {
        Thread caller = Thread.currentThread();
        CountDownLatch hold = new CountDownLatch(1);
        ThreadPoolExecutor pool = new ThreadPoolExecutor(
                1, 1, 0, TimeUnit.MILLISECONDS,
                new ArrayBlockingQueue<>(1),
                task -> new Thread(task),
                new ThreadPoolExecutor.CallerRunsPolicy());

        pool.execute(() -> { try { hold.await(); } catch (InterruptedException e) { } });
        pool.execute(() -> System.out.println("task given to the pool     -> " + who(caller)));
        pool.execute(() -> System.out.println("rejected in a full pool    -> " + who(caller)));

        hold.countDown();
        pool.shutdown();
        pool.awaitTermination(10, TimeUnit.SECONDS);
    }
}
```

```
rejected in a full pool    -> caller
task given to the pool     -> created
```

The three `execute` calls are syntactically identical. The first call occupies the pool's single
thread with `hold.await()`. When the second call arrives, the thread is still busy but there is
an open spot in the queue — the task enters the queue and, later, after `hold.countDown()` is
called, runs on the pool's own thread: **created**. When the third call arrives, both the thread
is busy and the queue is full; the executor has nowhere to grow and the task falls to the
rejection policy. Since the policy is `CallerRunsPolicy`, the rejected task runs on the thread
that wrote the `execute` call, **right there**, synchronously — this is why that line appears
first in the output: the third task runs before `hold.countDown()` is called, before the second
task has even left the queue. There is no branching in the code at all; the only thing creating
the difference is the fullness at the moment of the call.

#### The Rejection Policy Is a Parameter Chosen by the Caller

`CallerRunsPolicy` silently loads the rejected task onto the caller; this is only one of four
ready-made options. The standard library defines three more: one silently drops the rejected
task without ever running it, one discards the **oldest** waiting task in the queue and takes the
new arrival in, and one — shown here — throws an exception. All four answer the same moment —
thread busy, queue full — with a different decision; which one gets chosen comes not from the
task's kind but from whether the party building the executor can afford to lose that task.

**CC19.** When a bounded queue is full and the pool is at its upper bound, an incoming task
falls to the rejection policy; the first task is deliberately held with a `CountDownLatch` so
that the queue's and the pool's fullness is modeled at run time.
**CC20.** The rejection policy is a parameter chosen by the party that builds the executor, not
determined by the task; the same moment of fullness gives two different results under two
different policies.

```java
// RejectionPolicy.java — the same full-pool moment gets two different results from two rejection policies
import java.util.concurrent.*;

public class RejectionPolicy {
    static String who(Thread caller) {
        return Thread.currentThread() == caller ? "caller" : "created";
    }

    static ThreadPoolExecutor fullPool(RejectedExecutionHandler policy, CountDownLatch hold) {
        ThreadPoolExecutor pool = new ThreadPoolExecutor(
                1, 1, 0, TimeUnit.MILLISECONDS,
                new ArrayBlockingQueue<>(1),
                task -> new Thread(task),
                policy);
        pool.execute(() -> { try { hold.await(); } catch (InterruptedException e) { } });
        pool.execute(() -> { });
        return pool;
    }

    public static void main(String[] args) throws Exception {
        Thread caller = Thread.currentThread();

        CountDownLatch hold1 = new CountDownLatch(1);
        ThreadPoolExecutor callerRunsPool = fullPool(new ThreadPoolExecutor.CallerRunsPolicy(), hold1);
        try {
            callerRunsPool.execute(() -> System.out.println("CallerRunsPolicy  -> " + who(caller)));
        } finally {
            hold1.countDown();
            callerRunsPool.shutdown();
            callerRunsPool.awaitTermination(10, TimeUnit.SECONDS);
        }

        CountDownLatch hold2 = new CountDownLatch(1);
        ThreadPoolExecutor abortsPool = fullPool(new ThreadPoolExecutor.AbortPolicy(), hold2);
        try {
            abortsPool.execute(() -> System.out.println("this line is never printed"));
            System.out.println("AbortPolicy       -> did not drop");
        } catch (RejectedExecutionException e) {
            System.out.println("AbortPolicy       -> " + e.getClass().getSimpleName());
        } finally {
            hold2.countDown();
            abortsPool.shutdown();
            abortsPool.awaitTermination(10, TimeUnit.SECONDS);
        }
    }
}
```

```
CallerRunsPolicy  -> caller
AbortPolicy       -> RejectedExecutionException
```

The same fullness setup — one task occupying the thread, one task waiting in the queue — is
identical in both executors. The only thing that changes is the fourth parameter given to the
constructor. `CallerRunsPolicy` silently hands the rejected task to the caller; `AbortPolicy`
turns the same moment into an exception, and the rejected task **never runs** — the third line
is never printed, and the `catch` block confirms it. Both are a response to the "full" state, but
the response is not chosen by the task, it is chosen by the code that builds the executor.

Why bother with this decision instead of leaving the queue and the pool unbounded? Because every
thread carries a cost — its own call frame, its own stack, a record the scheduler keeps track
of. An unbounded queue only postpones this problem: if task production is faster than
consumption, the queue grows forever and memory runs out; an unbounded pool carries the same
problem over to the thread count instead. A bounded queue and a bounded pool make the moment of
fullness **visible** and force a decision — the rejection policy is the name of that decision.

#### The Exception's Path: execute or submit

If a task throws an exception while running, where does that exception land? The answer depends
on which method the task was given through — and only one of the two methods shows the exception
on its own.

**CC21.** An exception thrown by a task given with `execute` reaches that thread's uncaught
exception handler. An exception thrown by a task given with `submit` is stored in the future
object and reaches the caller only if `get` is called; if `get` is never called, the exception
never shows up anywhere.

```java
// ExceptionPath.java — an exception thrown via execute shows up on its own, one via submit stays silent until get is called
import java.util.concurrent.*;
import java.util.concurrent.atomic.*;

public class ExceptionPath {
    public static void main(String[] args) throws Exception {
        AtomicInteger caught = new AtomicInteger(0);
        CountDownLatch doneA = new CountDownLatch(1);

        ExecutorService poolA = Executors.newSingleThreadExecutor(task -> {
            Thread t = new Thread(task);
            t.setUncaughtExceptionHandler((th, error) -> caught.incrementAndGet());
            return t;
        });
        poolA.execute(() -> {
            try {
                throw new RuntimeException("task error");
            } finally {
                doneA.countDown();
            }
        });
        doneA.await();
        poolA.shutdown();
        poolA.awaitTermination(10, TimeUnit.SECONDS);
        System.out.println("execute()  - reached the uncaught exception handler: " + (caught.get() == 1));

        ExecutorService poolB = Executors.newSingleThreadExecutor();
        Future<?> future = poolB.submit(() -> { throw new RuntimeException("task error"); });
        poolB.shutdown();
        poolB.awaitTermination(10, TimeUnit.SECONDS);
        System.out.println("submit()   - still silent after shutdown            : true (get not called)");
        try {
            future.get();
            System.out.println("get() result -> did not drop");
        } catch (ExecutionException e) {
            System.out.println("get() result -> " + e.getClass().getSimpleName()
                    + " wrapping: " + e.getCause().getClass().getSimpleName());
        }
    }
}
```

```
execute()  - reached the uncaught exception handler: true
submit()   - still silent after shutdown            : true (get not called)
get() result -> ExecutionException wrapping: RuntimeException
```

When a task given with `execute()` throws an exception, that exception reaches its own thread's
uncaught exception handler — with no `get` called at all, on its own. The same task given with
`submit()` throws the same exception, but the `Future` does not swallow that exception, it
**stores** it: even if the pool is shut down and awaited, the exception lands nowhere until
`get` is called on the future object. Once `get` is called, the exception is wrapped inside
`ExecutionException` and carried to the calling thread. Only one of the two ways of giving a task
shows the exception on its own — the count: **1** of **2**. This is also a warning about the
executor's own shutdown: `shutdown()` and `awaitTermination()` guarantee that tasks have
finished, but they say nothing about which task failed; that information only comes if it is
requested from the future objects.

The difference comes from how the two ways of giving a task wrap it. `execute(Runnable)` runs
the task directly; the thread's own run loop does not catch an exception coming out of the task,
it terminates the loop and carries it to the thread's uncaught exception handler. `submit(...)`
instead wraps the task in a future object first; that object's own run method catches every
exception coming out of the task **inside itself** and stores it, never letting it leak into the
thread's run loop — the loop continues as if the task finished successfully. Where the exception
lands is, therefore, a side effect of how the task was called: the same exception, thrown from
the same line, passes through two different paths.

All four measurements point the same way: giving a task to an executor service takes the choice
of who runs it out of the caller's hands, but it does not make it invisible. Distinct identity
count does not exceed the pool's size, the giving thread steps in in a full pool, the rejection
policy determines who meets this moment, and where the exception lands is also a consequence of
how it was given. In all four, a party that does not appear in the source line makes the
decision; this lesson brought that party into view every time, by running it.

#### Summary

- `ExecutorService` is an abstraction that decides for itself which thread runs a task;
  distinct thread identity stays bounded by the core count in a fixed pool, even if the task
  count exceeds that bound.
- While the pool is idle, the pool's own thread runs the task; while the pool is full, the same
  `execute` call runs the task on the **giving** thread — the code is identical, the fullness
  at the moment of the call makes the difference.
- The rejection policy is a parameter chosen not by the task but by the party building the
  executor; `CallerRunsPolicy` hands the task to the caller, `AbortPolicy` turns the same
  moment into an exception and the task never runs.
- An exception thrown via `execute` reaches the uncaught exception handler on its own; the same
  exception thrown via `submit` is stored in the future object and only reaches the caller if
  `get` is called.
- `ExecutorService`'s `AutoCloseable` implementation joins the try-with-resources pattern; the
  closing call is automatic for the executor too.

#### Next Step

In this lesson, the pool's threads were a bounded resource: a fixed number existed, they filled
up, they were reused in turn, and fullness put the caller to work. This bound comes from the
thread itself being **expensive**. The next lesson lifts that premise: the same question is asked
with an executor that uses a far cheaper kind of thread instead of a platform thread — if the
thread itself is cheap instead of the pool, how does the question of who runs the task change?

### Virtual Threads

Source: https://academia.sh/en/courses/jvm-and-concurrency/virtual-threads

For the same task set, distinct identity stays bounded at four in a fixed pool but grows with the task count in an executor that gives one virtual thread per task; a virtual thread is a real Thread distinguished with isVirtual(), not a platform thread; the carrier cannot be observed; and ThreadLocal keeping a separate value per virtual thread is a cost.

The previous lesson treated the pool's threads as a bounded resource: a fixed number existed,
they filled up, they were reused in turn, and fullness put the caller to work. This bound came
from the platform thread itself being expensive — each one carries an allocation from the
operating system and a scheduling record. This lesson asks the same question under an entirely
different premise: what if the thread itself is cheap? A virtual thread is a far cheaper kind of
thread that comes from the virtual machine, not the operating system. The same task set is run
on two separate executors and the same question is asked again — which thread runs this step —
but this time **how many distinct** threads it can produce is also measured.

In the previous lesson, the executor interface stayed fixed and only the number given to the
constructor changed. In this lesson the interface is the same again — `ExecutorService`,
`submit`, `invokeAll` — but the executor itself produces threads from an entirely different
source. That two different production mechanisms can stand behind the same interface is the
first thing this lesson measures.

#### The Same Task Set on Two Executors: Distinct Identity Count

The previous lesson's distinct identity counting technique carries over unchanged: each task
adds the thread it ran on to a set using only its identity (`==` equivalence), no name or number
is printed.

**CC22.** Distinct identity counting also applies to virtual threads; as a subtype of the
`Thread` class, a virtual thread also carries an object identity and can be counted with the
same set.
**CC23.** An executor giving one virtual thread per task (`newVirtualThreadPerTaskExecutor`)
creates a **new** virtual thread for every task given to it; this is a structural rule of the
executor and does not depend on scheduling.

```java
// Identities.java — a set that counts distinct thread identity using == equivalence (helper source, no main)
class Identities {
    static java.util.Set<Thread> newSet() {
        return java.util.Collections.synchronizedSet(
                java.util.Collections.newSetFromMap(new java.util.IdentityHashMap<>()));
    }
}
```

```java
// TwoExecutors.java — the same task set produces separate distinct identity counts on a fixed pool and a virtual executor
import java.util.*;
import java.util.concurrent.*;

public class TwoExecutors {
    static int distinctIdentities(ExecutorService executor, int taskCount) throws Exception {
        Set<Thread> seen = Identities.newSet();
        List<Callable<Void>> tasks = new ArrayList<>();
        for (int i = 0; i < taskCount; i++) {
            tasks.add(() -> { seen.add(Thread.currentThread()); return null; });
        }
        executor.invokeAll(tasks);
        return seen.size();
    }

    public static void main(String[] args) throws Exception {
        try (ExecutorService fixed4 = Executors.newFixedThreadPool(4);
             ExecutorService virtual = Executors.newVirtualThreadPerTaskExecutor()) {
            System.out.println("4 tasks,  fixed pool(4)    -> distinct identities: " + distinctIdentities(fixed4, 4));
            System.out.println("4 tasks,  virtual executor -> distinct identities: " + distinctIdentities(virtual, 4));
        }
        try (ExecutorService fixed4b = Executors.newFixedThreadPool(4);
             ExecutorService virtualb = Executors.newVirtualThreadPerTaskExecutor()) {
            System.out.println("20 tasks, fixed pool(4)    -> distinct identities: " + distinctIdentities(fixed4b, 20));
            System.out.println("20 tasks, virtual executor -> distinct identities: " + distinctIdentities(virtualb, 20));
        }
    }
}
```

```
4 tasks,  fixed pool(4)    -> distinct identities: 4
4 tasks,  virtual executor -> distinct identities: 4
20 tasks, fixed pool(4)    -> distinct identities: 4
20 tasks, virtual executor -> distinct identities: 20
```

With four tasks, both executors produce four distinct identities — when the task count equals
the pool size, no difference shows up. With twenty tasks, the difference opens up: the fixed
pool stays at four distinct identities **again**, because the rule from the previous lesson
still holds here — pool size is an upper bound independent of how many tasks arrive. The virtual
executor, on the other hand, produces exactly **twenty** distinct identities — every task finds
its own virtual thread, none of them waits for a previous one and takes its place. The number
grows together with the task count, because a virtual thread executor is not a pool — it is a
mechanism that **spawns** a new thread for every task (CC23).

The number twenty was not chosen at random: four does not show the difference, because it
coincides with the pool size; twenty, being several times the pool size, makes visible the fact
that the fixed pool hits its **ceiling** and stays there. In the previous lesson, this ceiling
was measured through fullness — an `execute` call falling to the caller; here, the same ceiling
is measured as a production limit, through distinct identity count.

#### A Virtual Thread Is Still a Thread

Whether the thing driving up the distinct identity count is really a thread can be verified
separately.

**CC24.** Whether a thread is virtual is distinguished **only** by `Thread.isVirtual()` — not by
name, priority, or any other observation.

```java
// VirtualCheck.java — a virtual thread is a real Thread distinguished by Thread.isVirtual()
import java.util.*;
import java.util.concurrent.*;
import java.util.concurrent.atomic.*;

public class VirtualCheck {
    static int virtualCount(ExecutorService executor, int taskCount) throws Exception {
        AtomicInteger virtual = new AtomicInteger(0);
        List<Callable<Void>> tasks = new ArrayList<>();
        for (int i = 0; i < taskCount; i++) {
            tasks.add(() -> {
                if (Thread.currentThread().isVirtual()) virtual.incrementAndGet();
                return null;
            });
        }
        executor.invokeAll(tasks);
        return virtual.get();
    }

    public static void main(String[] args) throws Exception {
        try (ExecutorService fixed4 = Executors.newFixedThreadPool(4)) {
            System.out.println("fixed pool(4), 4 tasks  -> ran on a virtual thread: "
                    + virtualCount(fixed4, 4) + "/4");
        }
        try (ExecutorService virtual = Executors.newVirtualThreadPerTaskExecutor()) {
            System.out.println("virtual executor, 4 tasks -> ran on a virtual thread: "
                    + virtualCount(virtual, 4) + "/4");
        }
        Thread mainThread = Thread.currentThread();
        System.out.println("is the main thread virtual                             : "
                + mainThread.isVirtual());
    }
}
```

```
fixed pool(4), 4 tasks  -> ran on a virtual thread: 0/4
virtual executor, 4 tasks -> ran on a virtual thread: 4/4
is the main thread virtual                             : false
```

None of the fixed pool's four tasks run on a virtual thread, and all four of the virtual
executor's do; the task running the code is the **same** lambda in both cases, only the executor
changes. The thread running the `main` method is not virtual either — the program itself always
starts from a platform thread. A virtual thread exists as a subtype of the `Thread` class: it
shares the same `start`, `join`, `interrupt` calls, the same lifecycle, but it is **not a
platform thread** — that is, it does not correspond one-to-one to a thread allocated by the
operating system. This distinction connects directly to the number from the previous section: the
virtual executor could produce twenty distinct identities because what it produces is not a
resource allocated by the operating system, it is an object the virtual machine manages on its
own. `isVirtual()` is the only way to distinguish the two kinds; since both come from the same
superclass and carry the same methods, no other call gives this distinction.

#### A Blocked Virtual Thread Dismounts Its Carrier

If there is no platform thread, how does a virtual thread's code run? A virtual thread mounts
onto a **carrier thread** while it runs — the code actually runs on a platform thread, and the
virtual thread borrows that platform thread. When a virtual thread is **blocked** in a call like
`CountDownLatch.await()`, it dismounts from its carrier: the platform thread underneath it is
freed and can run another virtual thread's code. Once the blocking ends, the virtual thread
mounts a carrier again — not necessarily the same one.

Mounting and dismounting is managed by the mechanism itself in standard-library calls like
`CountDownLatch`, locks, and blocking I/O; the caller does not set this mechanism up, it just
calls `await()` or `get()` as usual. How many times a given virtual thread mounts and dismounts
is also something that cannot be observed — this lesson does not measure that number either, it
only measures how many distinct virtual thread identities can exist **at the same time**.

**The carrier thread cannot be observed** — this lesson neither prints nor counts it; what is
measured is only the distinct count of **virtual** thread identity. **CC25.** Fifty tasks are all
set up to be blocked on a single `CountDownLatch` and released at the same time; the release
call is made right after all the tasks have been submitted, so the delay is not a race —
whenever a waiting task checks, once the `CountDownLatch` reaches zero it sees the same answer.

```java
// BlockedTasks.java — fifty blocking tasks produce four distinct identities on a fixed pool, fifty on a virtual executor
import java.util.*;
import java.util.concurrent.*;

public class BlockedTasks {
    static int blockedDistinctIdentity(ExecutorService executor, int taskCount) throws Exception {
        Set<Thread> seen = Identities.newSet();
        CountDownLatch release = new CountDownLatch(1);
        List<Future<?>> futures = new ArrayList<>();
        for (int i = 0; i < taskCount; i++) {
            futures.add(executor.submit(() -> {
                try {
                    release.await();
                } catch (InterruptedException e) { }
                seen.add(Thread.currentThread());
            }));
        }
        release.countDown();
        for (Future<?> f : futures) f.get();
        return seen.size();
    }

    public static void main(String[] args) throws Exception {
        int taskCount = 50;
        try (ExecutorService fixed4 = Executors.newFixedThreadPool(4)) {
            System.out.println(taskCount + " blocking tasks, fixed pool(4) -> distinct identities: "
                    + blockedDistinctIdentity(fixed4, taskCount));
        }
        try (ExecutorService virtual = Executors.newVirtualThreadPerTaskExecutor()) {
            System.out.println(taskCount + " blocking tasks, virtual executor -> distinct identities: "
                    + blockedDistinctIdentity(virtual, taskCount));
        }
    }
}
```

```
50 blocking tasks, fixed pool(4) -> distinct identities: 4
50 blocking tasks, virtual executor -> distinct identities: 50
```

All fifty tasks block at a single lock point, then are all released together. On the fixed pool,
four platform threads process these fifty tasks in turn, four at a time — distinct identity stays
at four. On the virtual executor, all fifty find their own virtual thread and all finish — none
of them waits in a queue for lack of a thread. This shows that a carrier pool built with four
platform threads was able to accommodate fifty virtual threads — because a blocked virtual thread
releases its carrier. **Limiting measurement:** the growth of distinct identity count is not a
speed claim, it is a fact. No duration is measured in this lesson; the only thing measured is how
many distinct virtual thread identities could exist. Whether this turns into a gain is only
meaningful through the question of how many threads the blocking code would otherwise occupy, and
that question is **modeled** here, not run: all fifty tasks were able to reach an "at-the-same-
time blocked task" count exceeding the platform thread count, while on the fixed pool that
number could never be larger than four.

This modeling deliberately draws a limit: making the number fifty into a hundred or a thousand
does not change the result, it only repeats the same fact with a bigger number. The lesson stops
here rather than scaling it up, because the claim is not in the number itself, it is in the fact
that a ceiling the fixed pool can **never** cross does not exist at all on the virtual
executor. When a platform thread is blocked, it keeps holding its own stack and scheduling
record; when a virtual thread is blocked, it releases the platform thread underneath it, and
what remains behind is only a small amount of state kept by the virtual machine — how many times
the carrier changed hands is not counted in this lesson, but it is what stands behind the "fifty
finished" result above.

#### ThreadLocal Keeps a Separate Value per Task: A Cost

This abundance of virtual threads has a cost. `ThreadLocal` is a structure that gives each
thread its own value; the value is produced with `initialValue` on the first `get()` call and
stays tied to that thread.

```java
// ThreadLocalCost.java — ThreadLocal produces a separate value per virtual thread, shared in a fixed pool
import java.util.*;
import java.util.concurrent.*;
import java.util.concurrent.atomic.*;

public class ThreadLocalCost {
    static int producedValueCount(ExecutorService executor, int taskCount) throws Exception {
        AtomicInteger counter = new AtomicInteger(0);
        ThreadLocal<Integer> own = ThreadLocal.withInitial(counter::incrementAndGet);
        List<Callable<Void>> tasks = new ArrayList<>();
        for (int i = 0; i < taskCount; i++) {
            tasks.add(() -> { own.get(); return null; });
        }
        executor.invokeAll(tasks);
        return counter.get();
    }

    public static void main(String[] args) throws Exception {
        int taskCount = 10;
        try (ExecutorService fixed4 = Executors.newFixedThreadPool(4)) {
            System.out.println(taskCount + " tasks, fixed pool(4) -> produced values: "
                    + producedValueCount(fixed4, taskCount));
        }
        try (ExecutorService virtual = Executors.newVirtualThreadPerTaskExecutor()) {
            System.out.println(taskCount + " tasks, virtual executor -> produced values: "
                    + producedValueCount(virtual, taskCount));
        }
    }
}
```

```
10 tasks, fixed pool(4) -> produced values: 4
10 tasks, virtual executor -> produced values: 10
```

The `own` variable is a single `ThreadLocal` object, read from the same code line in both cases.
On the fixed pool, ten tasks share four platform threads; each platform thread produces its own
value once and **reuses** it on later tasks — four values are produced in total. On the virtual
executor, since each of the ten tasks runs on its own virtual thread, `initialValue` is called
ten times, and no value is shared at all. This is a result that points the same way as the
previous section's distinct identity count, but needs to be read in reverse: the abundance of
virtual threads makes code using `ThreadLocal` produce that same abundance of output. Running one
virtual thread per task can mean setting up one `ThreadLocal` value per task — this is a cost,
and it can go unnoticed with large task counts.

Read together, the four measurements bring the course to a claim: a virtual thread makes the
thread itself cheaper, but it does not make everything that **comes with** a thread cheaper.
`ThreadLocal` is the one measured example of this; any other structure that grows with the
thread count can carry the same cost. As distinct identity count grows, this cost grows with it —
like the gain itself, the cost too is a fact, and needs to be measured separately.

#### Summary

- The same task set produces a distinct identity count bounded by pool size on a fixed pool,
  but one that grows with the task count on an executor giving one virtual thread per task.
- A virtual thread is a real `Thread` distinguished with `Thread.isVirtual()`; it shares the
  same lifecycle API but does not correspond one-to-one to a platform thread.
- A blocked virtual thread dismounts its carrier and frees the platform thread; the carrier
  cannot be observed, and the only thing measured is distinct virtual identity count.
- The growth of distinct identity count is not a speed claim, it is a fact; the gain is only
  meaningful through the question of how many threads the blocking code would otherwise occupy,
  and that question is modeled, not run.
- `ThreadLocal` shares its value across threads in a fixed pool, but produces a separate value
  per task on a virtual executor; this is a cost carried by the abundance of virtual threads.

#### Next Step

This lesson made the thread itself cheaper and showed that spawning one per task is possible.
But every task so far has been independent — one did not start until another finished. The next
lesson strings independent tasks into a **chain**: one step's result becomes the next step's
input. The question of who runs these chained stages is now asked not for a single task, but for
a sequential series.

### Asynchronous Composition

Source: https://academia.sh/en/courses/jvm-and-concurrency/asynchronous-composition

Measures, in a future-based chain, which side runs each stage — the caller on a completed future, the completing side on a pending future, a pool we never created or a given one on an async-suffixed call — and that the same chain written four ways runs on four different sides while the result stays the same in all four. Shows that when a stage drops, the following stages are skipped and the error reaches the caller wrapped at join.

The previous lesson made independent tasks cheaper; this lesson strings them into a **chain**.
One stage's result becomes the next stage's input, and even though each link works correctly on
its own, stringing them into a chain raises a new question: which thread runs this stage?
Future-based chaining asks this question again at every binding point, and the four answers the
course has measured all show up together, on the same chain, right here.

#### The Same Chain Runs on Four Different Sides in Four Different Forms

A stage binding to a `CompletableFuture` that is already completed runs **synchronously** on the
thread making the binding call; if the future is not yet completed, the stage runs on the
completing side's thread. An `Async`-suffixed binding opens a third path: on a common pool if no
executor is given, on the given executor if one is.

**CC26.** If the future a stage is bound to is already completed, the stage runs synchronously
on the thread making the binding call; if the future is not yet completed, the stage runs on the
thread that completes it.
**CC27.** An `Async`-suffixed stage runs, if no executor is given, on a common pool the program
never created (unknown); if an executor is given, on that executor's thread (created).

```java
// Run.java — which thread runs a step: the caller, a created thread, or a party we never created (helper source, no main)
import java.util.*;

class Run {
    static final Map<Thread, String> labels = Collections.synchronizedMap(new IdentityHashMap<>());

    static void announce(String label) { labels.put(Thread.currentThread(), label); }

    static String who() {
        String l = labels.get(Thread.currentThread());
        return l == null ? "unknown" : l;
    }

    static void observe(String point) {
        System.out.printf("%-32s -> %s%n", point, who());
    }
}
```

```java
// FourStyles.java — the same chain runs on four different sides in four different forms, the result the same in all four
import java.util.*;
import java.util.concurrent.*;

public class FourStyles {
    static int stageFunction(int v) { return v * 2 + 1; }

    public static void main(String[] args) throws Exception {
        Run.announce("caller");
        List<Integer> results = new ArrayList<>();

        // A. completed future: the stage runs on the caller
        int a = CompletableFuture.completedFuture(10)
                .thenApply(v -> { Run.observe("completed future stage"); return stageFunction(v); })
                .join();
        results.add(a);

        // B. pending future: the completing thread runs the stage
        CompletableFuture<Integer> pending = new CompletableFuture<>();
        CompletableFuture<Integer> chain = pending.thenApply(v -> {
            Run.observe("pending future stage"); return stageFunction(v);
        });
        Thread completer = new Thread(() -> { Run.announce("created"); pending.complete(10); });
        completer.start();
        completer.join();
        results.add(chain.join());

        // C. async on the default pool: on a thread we never created
        int c = CompletableFuture.completedFuture(10)
                .thenApplyAsync(v -> { Run.observe("async on default pool"); return stageFunction(v); })
                .join();
        results.add(c);

        // D. async on a given pool: on the thread of the executor we built
        ExecutorService given = Executors.newSingleThreadExecutor(
                task -> new Thread(() -> { Run.announce("created"); task.run(); }));
        int d = CompletableFuture.completedFuture(10)
                .thenApplyAsync(v -> { Run.observe("async on given pool"); return stageFunction(v); }, given)
                .join();
        results.add(d);
        given.shutdown();

        boolean allSame = results.stream().distinct().count() == 1;
        System.out.println("result of the four forms (10*2+1=21)  : " + results);
        System.out.println("all the same                          : " + allSame);
    }
}
```

```
completed future stage           -> caller
pending future stage             -> created
async on default pool            -> unknown
async on given pool              -> created
result of the four forms (10*2+1=21)  : [21, 21, 21, 21]
all the same                          : true
```

Four lines gather the course's four separate answers into a single chain. On a completed future,
`thenApply` runs the moment it is bound, because there is already something to run — the stage
runs on whichever thread made the binding call: the **caller** (CC26). On a pending future, the
stage stays waiting; it does not run on any thread until `pending.complete(10)` is called, and
when that call comes, it runs on the thread that **made** it — here that side is the thread we
built named `completer`, that is, **created** (CC26). On the third line, since no executor is
given to the added suffix, the stage runs on a common pool; we neither built nor named this
pool, which is why it is **unknown** (CC27). On the fourth line, the same suffix is this time
given an executor, and the stage runs on that executor's — our own, built by us — thread:
**created** (CC27).

#### Adding the Suffix Changes Which Side Runs It, Not the Speed

All four forms apply exactly the same operation: `v -> v * 2 + 1`, on the same starting value.
The result list is `[21, 21, 21, 21]` — all four reach the same number. This is the lesson's
limiting measurement: the difference between `thenApply` and `thenApplyAsync` is not in the
computed **value**, it is in **who** computes that value. Adding the suffix did not speed
anything up — no duration was measured in this lesson, nor can it be — the only thing that
changed was which side the call syntax chooses. The reason to write a stage with the `Async`
suffix is not speed, it is to **separate** that stage from the caller's own thread — for
example, if the caller is a user-interface thread, or does not want to hold a potentially
long-running task on itself. The decision itself is not this lesson's subject; only its
**result**, which side ran it, is measured.

#### When a Stage Drops, the Following Ones Are Skipped

What happens if a stage in the chain throws an exception? The following stages are never called
at all, and the error is carried all the way to the `join()` call at the end of the chain.

**CC28.** In a `thenApply` chain, every stage runs synchronously on the thread the previous one
completed on; this chain has no `Async` suffix at all, so which thread the error path runs on
needs no separate modeling — on an already-completed future, that thread is already the caller.

```java
// ErrorPath.java — when a stage drops, the following stages are skipped, the error reaches the caller wrapped at join
import java.util.concurrent.*;
import java.util.concurrent.atomic.*;

public class ErrorPath {
    public static void main(String[] args) throws Exception {
        AtomicInteger ranStages = new AtomicInteger(0);

        CompletableFuture<Integer> chain = CompletableFuture.completedFuture(10)
                .thenApply(v -> { ranStages.incrementAndGet(); return v + 1; })
                .thenApply(v -> { if (v > 0) throw new RuntimeException("stage error"); return v; })
                .thenApply(v -> { ranStages.incrementAndGet(); return v + 1; })
                .thenApply(v -> { ranStages.incrementAndGet(); return v + 1; });

        try {
            chain.join();
            System.out.println("chain did not drop");
        } catch (CompletionException e) {
            System.out.printf("%-32s -> %s wrapping: %s%n", "error caught at join",
                    e.getClass().getSimpleName(), e.getCause().getClass().getSimpleName());
        }

        int stagesDefinedAfterError = 2;
        int stagesRanAfterError = ranStages.get() - 1;
        System.out.printf("%-32s -> %d%n", "stage ran before error", 1);
        System.out.printf("%-32s -> %d%n", "stages defined after error", stagesDefinedAfterError);
        System.out.printf("%-32s -> %d%n", "stages skipped after error",
                stagesDefinedAfterError - stagesRanAfterError);
    }
}
```

```
error caught at join             -> CompletionException wrapping: RuntimeException
stage ran before error           -> 1
stages defined after error       -> 2
stages skipped after error       -> 2
```

The chain was built from four stages: the first ran normally, the second threw an exception, the
third and fourth never ran at all. The counter confirms this — **one** stage ran before the
error, and of the **two** stages defined after it, **both** were skipped. A `thenApply` chain
treats the rest as automatically **dropped** once one stage drops; every stage assumes the
previous one completed successfully, and once that assumption breaks, the rest of the chain is
never evaluated at all. The error is carried silently all the way to the `join()` call, and there
it reaches the caller wrapped inside a `CompletionException`; the original exception can be
recovered with `getCause()`. This behavior points the same way as the previous lesson's
`submit`/`get` pair: an exception can stay silent up to the first point the form it was given in
allows.

The two lessons' way of carrying an exception is close but not identical. The previous lesson
wrapped the exception in a checked `ExecutionException` when `Future.get()` was called; this
lesson does the same job with an unchecked `CompletionException` when `join()` is called. Both
give back the real exception with `getCause()`, both cross from the side the task/stage ran on
to the caller's side with a wrapping — the only difference is which call (`get` or `join`) chose
which wrapping (checked or unchecked).

#### Summary

- If the future a stage is bound to is already completed, it runs synchronously on the thread
  making the binding call; if the future is not yet completed, the stage runs on the thread
  that completes it.
- An `Async`-suffixed stage runs, if no executor is given, on a common pool the program never
  created; if one is given, on that given executor's thread.
- When the same chain is built in four separate forms, four separate sides run it, but since the
  operation applied is the same, the result stays exactly the same across all four; adding the
  suffix changes which side runs it, not the speed.
- When a stage throws an exception, every following stage in the chain is skipped; the error is
  carried silently to the `join()` call and reaches the caller there, wrapped inside a
  `CompletionException`.
- In a cut-off chain, the stages before the error ran normally; the number of skipped stages
  equals the length of the rest of the chain.

#### Course Wrap-Up

**CC29.** The running-side column in the wrap-up table takes only three values: caller, created,
unknown — these are the three answers the course's shared definition builds, and they stay fixed
throughout the course.

The course opened with a single question: which thread runs this step, and who created that
thread? Eleven lessons asked this question across eleven separate mechanisms, and each time got
the answer by running at least two ways of doing it side by side. The first claim was confirmed
and shown once more in this lesson: the running thread does not show up in the call syntax —
the difference between `thenApply` and `thenApplyAsync` is a suffix, but the running side splits
three ways. The second claim — that a thread the program never created can run its own code —
was exemplified once more here, with the common pool. The third claim, K04's own distinction
between "who runs it" and "who sees it," was measured in earlier lessons through the memory model
and `ThreadLocal`; this lesson did not test that distinction, it only tested the running side.
The course's boundary was also the same from the start, and it was confirmed once more here:
adding a suffix, growing a pool, using a virtual thread — none of these is a speed claim on its
own; everything measured was a **count**, never a **duration**.

| Lesson | Behavior measured | Running side (caller · created · unknown) | Limiting measurement |
|---|---|---|---|
| Class Loading | Three of four triggers (a new instance, a static method, reading a static field) run class initialization; when two threads touch the same class at the same time, only one of them runs the initialization block | caller (four triggers) · created (winner in concurrent touch) | Reading a compile-time constant never initializes the class at all; a blank line means "not triggered," not "did not happen" |
| Memory Areas | Of six values, three (a local variable, a parameter, a thread-local value) never leave the writing thread at all, three (an object field, an array element, a static field) do | created (writer) · caller (reader, after join) | Being shared is not the same as being visible; without an ordering tool like join, the same read is undefined |
| Garbage Collectors | With a strong reference standing, cleanup runs in none of three collection requests; once the reference is removed, it runs. A weak and a phantom reference land in the queue, a soft one resists | unknown (the cleanup action) | System.gc() is a request; the measurement determines not when cleanup will run, only which side runs it |
| Just-in-Time Compilation | After the same method is called three hundred thousand times, the instruction list in the class file stays exactly the same; once the number of receiver types passing through a call site rises from one to two, the monomorphic assumption breaks | caller (producing the profile data) · unknown (making the decision) | Speed is not measured and cannot be; the only things counted are instruction count and receiver type count, and deoptimization is modeled |
| Threads and Lifecycle | The same `Runnable` body runs on the caller with `run()`, on a created thread with `start()`; of three interruption setups, only one fails to finish its body without an interruption request | caller (`run`) · created (`start`) | `start()` returning does not show that the body has run; order is built by hand with `CountDownLatch` and `join` |
| Java Memory Model | Of eight publication mechanisms, four ask both the writer and the reader to act; a `final` field puts the load on the writer alone, `join` on the reader alone; two (a field without synchronization, `ThreadLocal`) fall outside the visibility question | created (writer) · caller (reader) | The visibility defect of a field without synchronization is not shown by running it, it is modeled as a rule; `volatile` gives no atomicity |
| Synchronization | Re-entry depth rises from `0` to `2` and back down; only one of three lock setups shares a single lock, the other two raise the count of separate locks to two | caller (holding the lock) · created (waiting) | A lock gives mutual exclusion and visibility, not order among waiters; the fair-lock option is modeled |
| Concurrency Utilities | Compare-and-set reaches its result in three attempts under a modeled contention and never leaves the caller; four snapshots see, in order, 0, 1, 2, and 3 elements — the writer produces a new array on every change | caller (compare-and-set, traversal) · created (dropping the count, releasing the permit) | An atomic type gives atomicity only to a single variable; keeping two variables consistent together is explained by rule, not by running it |
| Executor Services | While idle, the pool's own thread runs it; while full, the giving side runs it; the rejection policy is a parameter chosen by the caller | caller (rejected in a full pool) · created (task given to the pool) | Only one of the two ways of giving a task (`execute`) shows the exception on its own; `submit` only shows it if `get` is called |
| Virtual Threads | For the same task set, distinct identity stays bounded at four in a fixed pool, but grows with the task count in a virtual executor; `ThreadLocal` producing a separate value per task | created (in both) | The growth of distinct identity count is not a speed claim, it is a fact; the carrier cannot be observed |
| Asynchronous Composition | The caller runs it on a completed future, the completing side on a pending future, an unknown or a given pool on a suffixed call; the stages after an error are skipped | caller (completed) · created (pending, given pool) · unknown (default pool) | Adding the suffix does not speed anything up, it only changes the running side; the result of the four forms stays exactly the same |

JVM, Concurrency and Performance closes here. The question the course asked — which thread runs
this step — sits under every line of a Java program at all times; the call syntax never answers
this question on its own.

