# Concurrency Basics

## 1. Threads: the Unit of Concurrent Execution

A `Thread` is an independent path of execution within a process, sharing the same heap memory as every other thread in that JVM. Two ways to define what a thread runs:

```java
// implementing Runnable — preferred, doesn't burn your one shot at extending a class
Runnable task = () -> System.out.println("running on: " + Thread.currentThread().getName());
Thread t = new Thread(task);
t.start();      // schedules the thread to run — NOT the same as calling task.run() directly
t.join();       // blocks the CALLING thread until t finishes

// extending Thread directly — works, but couples your class to being a Thread
class Worker extends Thread {
    public void run() { System.out.println("worker running"); }
}
new Worker().start();
```

Calling `.run()` directly executes the code synchronously on the *current* thread — no new thread is created. Only `.start()` actually schedules a new OS-backed thread.

## 2. Race Conditions: Why Shared Mutable State Is the Problem

A race condition happens when multiple threads read-modify-write shared state without coordination, and the outcome depends on unpredictable timing.

```java
class Counter {
    private int count = 0;
    void increment() { count++; } // NOT atomic — read, add 1, write back
    int get() { return count; }
}
```

`count++` is really three separate steps (read `count`, compute `count + 1`, write it back). If two threads interleave those steps, one increment can be lost entirely — thread A reads `5`, thread B reads `5` before A writes back, both compute `6`, both write `6`, and one increment vanishes. Running this with enough threads and iterations reliably produces a final count *less than* the true number of increments performed.

## 3. `synchronized`: Mutual Exclusion

`synchronized` ensures only one thread at a time can execute a given block/method for a given lock object, fixing the race above:

```java
class Counter {
    private int count = 0;
    synchronized void increment() { count++; } // lock is `this` — only one thread inside at a time
    synchronized int get() { return count; }
}
```

```java
class Counter {
    private final Object lock = new Object();
    private int count = 0;
    void increment() {
        synchronized (lock) { count++; } // synchronized block — lock on an explicit object
    }
}
```

A `synchronized` instance method locks on `this`; a `synchronized` static method locks on the `Class` object; a `synchronized (obj) { ... }` block locks on whatever object you name. Only one thread can hold a given lock at a time — any other thread calling a method synchronized on the same lock **blocks** until the lock is released.

## 4. Visibility: Why `volatile` Exists

Mutual exclusion (`synchronized`) solves *atomicity*. A separate problem is **visibility**: without synchronization, one thread's write to a shared variable is not guaranteed to ever become visible to another thread reading it, because each thread/CPU core may cache the variable rather than re-reading main memory every time.

```java
class Flag {
    private boolean running = true; // no volatile: another thread's write to `running`
                                      // might never become visible to a thread spinning here
    void stop() { running = false; }
    void loopUntilStopped() {
        while (running) { /* busy loop */ } // can spin FOREVER on a stale cached `true`
    }
}
```

```java
class Flag {
    private volatile boolean running = true; // volatile: every read sees the latest write,
                                               // every write is immediately visible to other threads
    void stop() { running = false; }
    void loopUntilStopped() {
        while (running) { /* now guaranteed to observe stop() eventually */ }
    }
}
```

`volatile` guarantees visibility and ordering for reads/writes of that single variable, but **not** atomicity for compound operations — `volatile int count; count++;` is still a race, because `++` is still three separate steps even though each individual read and write is now visible. `synchronized` (or `java.util.concurrent.atomic` classes) is still required for compound read-modify-write operations.

## 5. `ExecutorService`: Thread Pools Instead of Manual `Thread` Management

Creating a raw `Thread` per task doesn't scale — OS threads are expensive to create and there's no bound on how many pile up. `ExecutorService` manages a reusable pool of worker threads that tasks are submitted to:

```java
java.util.concurrent.ExecutorService pool = java.util.concurrent.Executors.newFixedThreadPool(4);

pool.submit(() -> System.out.println("task on a pooled thread"));

pool.shutdown(); // stop accepting new tasks; lets already-submitted tasks finish
```

Submitted tasks are queued and run by whichever pooled thread becomes free — you never manage `Thread` objects directly, and the pool size bounds how much concurrent work actually happens regardless of how many tasks are submitted.

## 6. `Future`: Getting a Result Back From an Async Task

`.submit()` on an `ExecutorService` returns a `Future<T>` representing a result that isn't ready yet:

```java
java.util.concurrent.ExecutorService pool = java.util.concurrent.Executors.newFixedThreadPool(2);

java.util.concurrent.Future<Integer> future = pool.submit(() -> {
    Thread.sleep(100);
    return 42;
});

System.out.println("submitted, doing other work...");
int result = future.get(); // BLOCKS the calling thread until the task finishes, then returns 42
pool.shutdown();
```

`.get()` blocks the caller until the task completes (or throws, wrapped as `ExecutionException`, if the task itself threw); an overload `.get(timeout, unit)` bounds how long the caller will wait before giving up with a `TimeoutException`.

## Under the Hood

**Threads are backed by real OS threads, and the JVM memory model is what `volatile`/`synchronized` actually control.** Each Java `Thread` maps to a native OS thread (on the HotSpot JVM), each with its own stack but sharing the process's heap. Because modern CPUs have per-core caches and compilers/CPUs are allowed to reorder instructions for performance as long as single-threaded behavior looks unchanged, a plain (non-volatile, non-synchronized) write on one thread has no guaranteed point at which another thread must observe it — the Java Memory Model (JMM) formally defines exactly which operations establish a **happens-before** relationship between threads. `synchronized` (both entering and exiting a monitor) and `volatile` reads/writes are two of the JMM's happens-before-establishing actions: exiting a `synchronized` block happens-before a subsequent thread entering a block synchronized on the *same* lock, and a `volatile` write happens-before any subsequent `volatile` read of the *same* variable. Without one of these, the JMM makes literally no visibility guarantee at all — not "probably fine," genuinely undefined, which is why the `Flag` example without `volatile` can spin forever on real hardware, not just in theory.

**`synchronized` is implemented via the object's intrinsic monitor, with biased/lightweight/heavyweight locking as a performance ladder.** Every Java object has an associated monitor (historically tracked via bits in the object header). Uncontended locking — the extremely common case where one thread repeatedly locks/unlocks an object nobody else is touching — is handled by the JVM with cheap, sometimes lock-free bookkeeping (biased or thin/lightweight locking, implementation-dependent by JVM version); only genuine contention, where a second thread actually tries to acquire a lock another thread holds, triggers an expensive "heavyweight" OS-level mutex/monitor with real thread blocking and a wait queue. This is why `synchronized` code with little real contention is often much cheaper than people assume, and why contended locks — not the mere presence of `synchronized` — are the actual performance concern.

![One thread's write can sit in its core's cache while another thread's JIT-hoisted read never sees it; volatile and synchronized establish happens-before edges that guarantee visibility](diagrams/01-memory-model.svg)

## Common Pitfalls

1. **Calling `.run()` instead of `.start()`** — executes the task synchronously on the current thread; no concurrency happens at all, and bugs from this are easy to miss because the code still "works," just sequentially.
2. **Assuming `volatile` makes compound operations (`count++`, `if (x) x = false;`) atomic** — it only guarantees visibility/ordering for single reads/writes; compound operations still need `synchronized` or an `AtomicInteger`/`AtomicBoolean`.
3. **Forgetting `pool.shutdown()`** — an `ExecutorService`'s threads are non-daemon by default, so a program that creates a thread pool and never shuts it down will hang on exit instead of terminating.
4. **Synchronizing on different lock objects for the same shared state** — `synchronized(new Object())` inside a method creates a fresh lock every call, providing zero actual mutual exclusion; the lock object must be the *same* instance across every thread/call site protecting that state.
5. **Calling `future.get()` immediately after `submit()` with no other work in between** — defeats the purpose of submitting asynchronously; if the calling thread has nothing else to do until the result arrives, an async `Future` bought nothing over just calling the task directly.

## Quick Reference

```java
new Thread(runnable).start();     // schedule on a new OS-backed thread
t.join();                          // block caller until t finishes

synchronized void m() { }           // lock on `this`
synchronized (obj) { }               // lock on an explicit object
volatile boolean flag;                // visibility + ordering, NOT atomicity, for one variable

ExecutorService pool = Executors.newFixedThreadPool(n);
Future<T> f = pool.submit(callable); // async task -> result-in-the-future handle
T result = f.get();                   // BLOCKS until the task completes
pool.shutdown();                       // stop accepting new tasks

Race condition: unsynchronized read-modify-write on shared state -> lost updates
happens-before: what the JMM actually guarantees; synchronized/volatile establish it
```
