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.
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".
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Coder Trainee
Experienced in Java and Python, we share and learn together. For submissions or collaborations, DM us.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
