January 23, 2026

Concurrency in Java Concurrency in Practice — Chapter 1 (A human-friendly blog + your notes, organized)

You’ve basically decoded what Chapter 1 is trying to do: it’s not teaching APIs yet — it’s building the mental model and warning labels.

Here’s a clean, interesting “blog-style” version that combines everything you covered (plus your added performance hazard point about context switching + runtime overhead), with a few extra hints where the book is implicitly going.


The world changed: CPUs stopped getting “faster” the old way

For a long time, performance improvements were “free”:

Then reality hit:

So chip makers started adding more cores instead of cranking frequency. That gives you a new deal:

You don’t automatically get faster. You get more total compute, but only if your software can use it concurrently/parallel.

That’s the big modern motivation: multicore is the default, and concurrency is how you cash that check.


A quick history lesson: from bare metal to “many things at once”

Early systems:

  • one machine, one program, one user, one “flow”

Then OSs evolved to run multiple programs “at once” (time-slicing). Why?

1) Resource utilization

If program A is waiting on disk/network, the CPU shouldn’t sit idle. Run program B.

2) Fairness

Multiple users/processes should get a fair share of machine time.

3) Convenience (this one matters more than people admit)

Humans think in independent activities:

  • boil water

  • while it boils, read the news

  • then make tea

That’s concurrency as a model—even if there’s only one CPU.


Threads: “lightweight processes” with shared memory

A thread is often called a “lightweight process,” but the key detail is:

Threads share

Threads do NOT share

This is why threads are powerful and dangerous:

Sharing memory makes communication easy… and bugs easy.

Because now two threads can touch the same object at the same time.


Why threads are useful (the 3 big wins)

1) Better responsiveness

UI example:

You already called this out with Swing’s Event Dispatch Thread (EDT) idea.

2) Better throughput / resource usage

If one thread blocks on I/O:

  • another thread can run

  • CPU stays busy

  • system handles more work per unit time

3) Simpler modeling (sometimes)

Even if the implementation is complex, the mental model can be simpler:

  • “each request is handled independently”

  • “each client gets a flow”

This is why classic servers used the thread-per-request model.


Concurrency is already everywhere in Java (even if you didn’t ask for it)

Chapter 1 is basically saying: you’re already in the concurrency game.

Examples:

So even “normal” code can become concurrent because the framework calls you from multiple threads.


The three categories of concurrency hazards

This is the part where the book goes: “Okay, threads are great… now here’s how they ruin your day.”

1) Safety hazards (correctness)

This is about bad things happening:

Classic example: count++ is not atomic.
It’s roughly:

  1. read count

  2. add 1

  3. write back

Two threads can interleave those steps and lose updates.

2) Liveness hazards (progress)

This is about nothing good happening even if nothing “crashes”.

You nailed the definition:

  • Safety: “nothing bad happens”

  • Liveness: “something good eventually happens”

Common liveness failures:

  • Deadlock (A waits for B, B waits for A)

  • Starvation (a thread never gets CPU/lock/resources)

  • Livelock (everyone keeps reacting and moving, but no progress)

3) Performance hazards (doing work… but slower 😭)

This is the one you asked to strengthen, and it’s a BIG deal in real systems.

Concurrency can absolutely improve throughput — but threads also introduce runtime overhead. If you overdo threads (or synchronize poorly), your app can get slower as you add “parallelism”.


Performance hazards: the concrete overhead threads introduce

Here’s the “meat” point you wanted added, expanded in practical terms.

A) Context switching overhead

When the scheduler suspends one thread and runs another, the system has to:

  • save registers / program counter / stack metadata

  • restore another thread’s execution context

  • update scheduling structures

A context switch isn’t “free.” If you have too many runnable threads, the CPU spends real time just juggling them.

B) Loss of locality (cache pain)

Modern CPUs are fast partly because of caches (L1/L2/L3).

When you switch threads frequently:

  • the next thread likely touches different memory

  • caches get “cold”

  • more cache misses → slower execution

  • branch prediction and pipeline friendliness also degrade

Result: CPU time goes into reloading data instead of doing your business logic.

C) Scheduling overhead

With lots of threads:

  • the OS/JVM does more bookkeeping

  • the scheduler spends more time deciding who runs next

  • your app does less useful work per second

D) Lock contention + coordination costs

Even without many threads, performance dies when:

  • many threads fight over the same lock

  • you serialize “parallel” work accidentally

  • you trigger blocking/wakeup storms

E) Memory overhead

Each platform thread costs:

  • stack memory

  • internal structures in OS + JVM

Thousands of threads can eat memory even if “idle”.

Big takeaway:

Threads can increase throughput… until they don’t. After a point, more threads = more overhead, less real work.

That’s why real systems care about:

  • choosing sane thread counts

  • using thread pools

  • minimizing contention

  • measuring with profilers instead of guessing


Why NIO shows up here (and what it means in plain English)

You mentioned the book talking about multiplexed I/O and then name-dropping Java NIO.

The point is:

  • classic java.io often blocks a thread per connection

  • with enough clients, thread-per-client becomes expensive (context switches + memory + scheduler)

NIO lets you handle lots of connections with fewer threads by using:

  • non-blocking channels

  • selectors (“tell me which sockets are ready”)

This is the event-loop style used by Netty/Undertow/etc.

Rule of thumb:

  • Many mostly-idle connections → NIO/event loop shines

  • Moderate concurrency or simpler workloads → blocking I/O + thread pools can be simpler and fine

  • Modern Java also adds virtual threads (huge topic later), which changes the tradeoffs again


Swing EDT: thread safety via confinement (why JTable isn’t thread-safe)

Swing’s approach is simple and strict:

  • Only the Event Dispatch Thread touches UI components

  • background threads do slow work

  • UI updates are posted back onto the EDT

This avoids races by design: don’t share UI state across threads.


Your RMI question: can the same remote method run simultaneously on the same remote object?

In typical Java RMI server implementations, yes—concurrent calls are possible.

What usually happens:

  • the RMI runtime uses threads to handle incoming calls

  • multiple clients can invoke the same remote method at the same time

  • if they target the same remote object instance, your method can be entered concurrently by multiple threads

So the remote object must be designed like any other shared object:

  • make it thread-safe, or

  • confine state, or

  • synchronize access appropriately

If you keep shared mutable state inside a remote object without protection, you can absolutely create race conditions.


Practical hints (Chapter 1 “rules of survival”)

These are the habits that save you later:

  • Treat any framework callback as “could be called concurrently” unless documented otherwise

  • Prefer immutable objects or thread confinement where possible

  • Use thread pools instead of unbounded thread creation

  • Avoid blocking in event loops (NIO/reactor style)

  • Don’t optimize by guesswork — measure contention, CPU, GC, queue depth, latency percentiles


Final block: Everything you learned so far, distilled

If you remember only this, you’re in great shape going into Chapter 2:

  1. Multicore is the norm because clock speed stopped scaling nicely. To use modern hardware well, software must exploit concurrency/parallelism.

  2. Threads share heap memory, which makes communication easy and bugs likely.

  3. Threads improve systems through:

    • Responsiveness (UI stays alive)

    • Throughput (overlap I/O waits and CPU work)

    • Simpler modeling (request-per-thread is easy to reason about)

  4. Concurrency hazards come in three families:

    • Safety: wrong results (races, broken invariants)

    • Liveness: no progress (deadlock, starvation, livelock)

    • Performance: slower due to overhead/coordination

  5. Performance hazards are real: context switches, scheduler overhead, memory overhead, cache locality loss, lock contention. More threads can make you slower, not faster.

  6. Java already uses concurrency under the hood (servlets, RMI, Swing, timers, JVM internals) — so you must assume concurrency exists even if you didn’t “create threads.”

  7. NIO exists because thread-per-connection doesn’t scale forever; multiplexed I/O lets fewer threads manage many connections.

  8. Swing is thread-safe by confinement: all UI work on the EDT; background threads do slow work; UI updates hop back to EDT.


If you want, for Chapter 2 I can turn this into a “mini checklist” you can apply to any code snippet you read:

  • What’s shared?

  • What’s mutable?

  • What are the invariants?

  • What’s the publication/visibility story?

  • What’s the contention story?

That’ll make the next chapter feel way less abstract.