Fundamentals 9 min read

Why Java’s synchronized Guarantees Strong Memory Consistency

This article explains how Java’s synchronized keyword relies on JVM monitorenter/monitorexit instructions to enforce strong memory consistency, compares execution results with and without synchronization, and details the underlying memory semantics, benefits, and performance drawbacks.

Xiaokun's Architecture Exploration Notes
Xiaokun's Architecture Exploration Notes
Xiaokun's Architecture Exploration Notes
Why Java’s synchronized Guarantees Strong Memory Consistency

Based on a previous article about the workings of the synchronized keyword, this piece explains that the keyword is implemented using the JVM monitor instructions monitorenter and monitorexit, and explores the memory‑semantic effects of locking and unlocking.

Working Memory vs. Main Memory

Main memory refers to the physical RAM of the operating system.

Working memory is defined by the Java Memory Model (JMM); each thread copies variables from main memory into its own stack‑based workspace.

CPU Cache and Memory Flow

The CPU cache (L1‑L3) is consulted on every read/write, which can cause cache‑inconsistency. To achieve strong consistency, the JMM allows threads to bypass the cache and read directly from main memory.

synchronized Code Demonstration

Scenario: a shared variable sharedVar is written by thread‑1 (500 ms delay) while thread‑2 reads after a 600 ms network delay, and thread‑3 reads normally.

public class Sync2memory {
    private static Integer sharedVar = 10;

    public static void main(String[] args) throws Exception {
        testForReadWrite();
        // testForReadWriteWithSync();
        TimeUnit.SECONDS.sleep(2L);
        System.out.printf("finish the thread task,the final sharedVar %s ....
", sharedVar);
    }

    private static void testForReadWriteWithSync() throws Exception {
        Thread thread1 = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    TimeUnit.MICROSECONDS.sleep(500L);
                    synchronized (sharedVar) {
                        System.out.printf("%s modify the shared var ...
", "thread-1");
                        sharedVar = 20;
                    }
                } catch (Exception e) { System.out.println(e); }
            }
        });
        Thread thread2 = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    TimeUnit.MICROSECONDS.sleep(600L);
                    synchronized (sharedVar) {
                        System.out.printf("%s read the shared var %s 
", "thread-2", sharedVar);
                    }
                } catch (Exception e) { System.out.println(e); }
            }
        });
        Thread thread3 = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    synchronized (sharedVar) {
                        System.out.printf("%s read the shared var %s 
", "thread-3", sharedVar);
                    }
                } catch (Exception e) { System.out.println(e); }
            }
        });
        thread2.start();
        thread3.start();
        thread1.start();
        thread1.join();
        thread2.join();
        thread3.join();
    }

    private static void testForReadWrite() throws Exception {
        Thread thread1 = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    TimeUnit.MICROSECONDS.sleep(500L);
                    System.out.printf("%s modify the shared var ...
", "thread-1");
                    sharedVar = 20;
                } catch (Exception e) { System.out.println(e); }
            }
        });
        Thread thread2 = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    TimeUnit.MICROSECONDS.sleep(600L);
                    System.out.printf("%s read the shared var %s 
", "thread-2", sharedVar);
                } catch (Exception e) { System.out.println(e); }
            }
        });
        Thread thread3 = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.printf("%s read the shared var %s 
", "thread-3", sharedVar);
            }
        });
        // thread1‑3 start and join …
    }
}

Result Without synchronized

thread-3 read the shared var 10
thread-1 modify the shared var to 20  ...
thread-2 read the shared var 10
finish the thread task,the final sharedVar 20 ....
Process finished with exit code 0

Analysis: thread‑3 reads before any write, which is normal. thread‑1 writes after 500 ms, but thread‑2 reads after 600 ms and still sees the old value (10), demonstrating a visibility problem caused by stale data in the working memory.

Result With synchronized

thread-3 read the shared var 10
thread-1 modify the shared var ...
thread-2 read the shared var 20
finish the thread task,the final sharedVar 20 ....
Process finished with exit code 0

Analysis: After the synchronized block, thread‑2 reads the updated value (20), showing that the lock forces the working memory to invalidate its cache and fetch the latest value from main memory, thus guaranteeing visibility.

Understanding synchronized Memory Semantics

The shared variable inside a synchronized block is invalidated in the thread’s working memory, causing subsequent reads to fetch from main memory and ensuring strong cache consistency.

synchronized solves the memory‑visibility issue for shared variables.

Internally, entering a synchronized block triggers the JVM’s monitorenter instruction, which loads the variable from main memory; exiting triggers monitorexit, which flushes the updated value back to main memory.

Drawbacks of synchronized

It uses a monitor (heavyweight lock), which can degrade performance because it trades speed for strong consistency.

Thread scheduling by the CPU adds additional context‑switch overhead.

Thank you for reading; if this helped, feel free to share.

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.

concurrencysynchronizedmemory-model
Xiaokun's Architecture Exploration Notes
Written by

Xiaokun's Architecture Exploration Notes

10 years of backend architecture design | AI engineering infrastructure, storage architecture design, and performance optimization | Former senior developer at NetEase, Douyu, Inke, etc.

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.