Fundamentals 8 min read

Deep Dive into Java Concurrency Part 1: Understanding the Core via JMM

This article launches a new series on Java concurrency, explaining why concurrent programming is hard due to hardware, OS and compiler effects, detailing the Java Memory Model’s happens‑before rules, memory barriers, volatile semantics, and comparing volatile with synchronized, plus a practical double‑checked locking example.

Coder Trainee
Coder Trainee
Coder Trainee
Deep Dive into Java Concurrency Part 1: Understanding the Core via JMM

Why Is Concurrency Programming So Hard?

The difficulty stems from the fact that hardware, the operating system, and the compiler all "deceive" us, causing visibility, atomicity, and ordering problems.

Visibility : One thread modifies a variable but another thread may not see the change, leading to data inconsistency.

Atomicity : Operations that appear atomic are actually broken into multiple instructions, allowing intermediate states to be observed.

Ordering : Compilers or CPUs may reorder instructions, producing execution orders that differ from the source code.

The core task of concurrent programming is to fully utilize CPU resources while guaranteeing correctness .

JMM Core Rules

The Java Memory Model (JMM) acts as the "constitution" for solving these problems. It mandates that reads and writes of shared variables obey the happens‑before relationship.

JMM enforces this through memory barriers , which prevent certain instruction reorderings and ensure visibility.

LoadLoad : prevents read‑read reordering ("read after read").

StoreStore : prevents write‑write reordering ("write after write").

LoadStore : prevents read‑write reordering.

StoreLoad : the strongest barrier, prevents write‑read reordering.

8 Happens‑Before Rules

Program order rule – actions in the same thread happen‑before later actions.

Monitor lock rule – an unlock happens‑before a subsequent lock on the same monitor.

Volatile variable rule – a write to a volatile field happens‑before any subsequent read of that field.

Thread start rule – Thread.start() happens‑before any action in the started thread.

Thread termination rule – all actions in a thread happen‑before another thread detects its termination.

Thread interruption rule – Thread.interrupt() happens‑before the interruption is observed.

Object finalization rule – object construction happens‑before its finalize() method.

Transitivity – if A happens‑before B and B happens‑before C, then A happens‑before C.

Volatile Visibility Guarantee

public class VisibilityDemo {
    private static boolean running = true;

    public static void main(String[] args) throws InterruptedException {
        // Thread 1: waits for running to become false
        new Thread(() -> {
            while (running) {
                // busy‑wait
            }
            System.out.println("Thread 1 exits");
        }).start();

        Thread.sleep(1000);

        // Thread 2: sets running to false
        new Thread(() -> {
            running = false;
            System.out.println("running set to false");
        }).start();
    }
}

Without volatile, Thread 1 may never see the change and loop forever. With volatile, the write is immediately flushed to main memory, and Thread 1 reads the updated value each time.

Volatile vs. Synchronized

Visibility : both guarantee.

Atomicity : volatile does not guarantee; synchronized does.

Ordering : both guarantee.

Performance : volatile is lightweight; synchronized is heavyweight.

When to Use Volatile

State flags – simple boolean switches.

Double‑checked locking (DCL) – the instance variable.

Read‑many/write‑few scenarios.

When Volatile Is Not Sufficient

// ❌ volatile does not guarantee atomicity
volatile int count = 0;
count++; // not atomic!
// Equivalent to:
// 1. read count
// 2. increment
// 3. write count
// In multithreaded contexts, updates can be lost.

Practical Example: Why DCL Needs Volatile

public class Singleton {
    // Why is volatile needed?
    private static volatile Singleton instance;

    public static Singleton getInstance() {
        if (instance == null) { // first check
            synchronized (Singleton.class) {
                if (instance == null) { // second check
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}

If volatile is omitted, the JVM may reorder the three steps of object creation (allocate memory, assign reference, initialize). Another thread could see a non‑null reference before the object is fully initialized, leading to use of a partially constructed instance.

Series Preview

Upcoming topics include:

JMM and happens‑before (this article).

Synchronized source‑level analysis.

Volatile and CAS underlying principles.

AQS deep dive.

ReentrantLock and Condition.

ThreadPool source analysis.

ConcurrentHashMap source analysis.

Practical concurrency pitfalls.

Next article: "Java Concurrency Deep Dive (Part 2): Synchronized Source‑Level Analysis".

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

JavaconcurrencyvolatileJMMhappens-beforesynchronizedmemory barriersdouble-checked locking
Coder Trainee
Written by

Coder Trainee

Experienced in Java and Python, we share and learn together. For submissions or collaborations, DM us.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.