Java threads execute concurrently inside the JVM process, sharing heap objects while keeping separate stacks, and synchronization controls both mutual exclusion and visibility.

Learning Question

What does the JVM add to ordinary thread execution?

A Java program can have many threads running inside one JVM process. Each thread has its own call stack, but the threads can share heap objects and class metadata. This shared state is powerful and dangerous because one thread’s writes are not automatically understood by another thread in every possible execution order.

The first mental model is:

Java threads share heap state, but synchronization is needed to coordinate access and visibility.

Per-Thread and Shared State

Each Java thread has:

  • its own Java stack
  • its own active stack frames
  • its own execution position
  • its own thread state

Threads in the same JVM process share:

  • heap objects
  • static fields
  • class metadata
  • JVM runtime services
  • process resources

This separation explains why local variables in one method invocation are naturally per-thread, while object fields and static fields can be shared.

Java Threads and OS Threads

In common HotSpot deployments, Java platform threads are backed by operating system threads.

The operating system scheduler decides when those threads run on CPU cores. The JVM adds Java-level thread abstractions, stack management, safepoint coordination, synchronization support, diagnostics, and runtime bookkeeping.

Newer Java versions also include virtual threads, which change the mapping between Java-level tasks and carrier threads. The durable point for this collection is broader: the JVM manages Java execution contexts, and those contexts interact with the operating system scheduler and shared runtime state.

Monitors and synchronized

Every Java object can be associated with monitor behavior.

The synchronized keyword uses monitor enter and monitor exit operations. A synchronized instance method locks the receiver object. A synchronized static method locks the Class object for the class.

For example:

synchronized (lock) {
    counter++;
}

Only one thread can hold that monitor at a time. Other threads trying to enter the same synchronized region must wait.

This gives mutual exclusion: only one thread executes the protected region for that monitor at a time.

Visibility

Synchronization is not only about preventing two threads from entering the same block together.

It also creates visibility guarantees. When a thread exits a synchronized block, writes made inside the block become visible in a defined way to another thread that later enters a synchronized block guarded by the same monitor.

Without synchronization, volatile, final-field guarantees, thread confinement, immutable data, or other safe publication mechanisms, one thread may not reliably observe another thread’s writes when you expect it to.

This is why “it worked in my test” is not enough for shared mutable state. Unsynchronized code can appear correct under one timing and fail under another.

volatile

A volatile field gives visibility and ordering guarantees for reads and writes of that field.

It is useful for simple state publication patterns, flags, and references where compound invariants are not being updated through multiple fields.

It is not a replacement for all locking. For example, counter++ is still a read-modify-write operation. Making counter volatile does not by itself make the increment atomic.

Thread Dumps

A thread dump shows Java threads and their current execution states.

Common states include:

  • RUNNABLE
  • BLOCKED
  • WAITING
  • TIMED_WAITING
  • TERMINATED

These states help diagnose lock contention, deadlocks, stuck waits, blocking I/O, thread pool exhaustion, or long-running work.

A thread dump is not a scheduler trace. It is a snapshot of thread state and stack frames at a moment in time.

Core Mental Model

Keep these boundaries separate:

  • Each thread has its own stack and execution path.
  • Threads share heap objects and class state.
  • synchronized gives mutual exclusion and visibility through monitors.
  • volatile gives visibility and ordering for field access, not general atomicity for compound actions.
  • Thread dumps show runtime thread snapshots, not the full history of scheduling.

Final Summary

Java concurrency is JVM-managed execution over shared runtime state.

To reason about it, separate per-thread stacks from shared heap objects, and treat synchronization as both an access-control mechanism and a visibility mechanism.