Backend Development 13 min read

Understanding Java Synchronized: Basic Usage, Underlying Mechanism, and Execution Results

This article explains the three forms of using the synchronized keyword in Java—method, static method, and block synchronization—demonstrates each with runnable code examples, analyzes the JVM monitorenter/monitorexit instructions, and clarifies why synchronized methods and blocks enforce mutual exclusion and visibility guarantees.

Selected Java Interview Questions
Selected Java Interview Questions
Selected Java Interview Questions
Understanding Java Synchronized: Basic Usage, Underlying Mechanism, and Execution Results

1. Basic Usage of Synchronized

Synchronized is the most common and simplest way to solve concurrency problems in Java. It mainly serves three purposes: ensuring mutual exclusion when accessing synchronized code, guaranteeing timely visibility of shared variable modifications, and effectively preventing instruction reordering.

There are three syntactic forms of synchronized:

Synchronizing an instance method

Synchronizing a static method

Synchronizing a code block

The following examples illustrate each form while keeping the rest of the code identical for easy comparison.

1) No synchronization

Code snippet 1:

package com.paddx.test.concurrent;

public class SynchronizedTest {
    public void method1() {
        System.out.println("Method 1 start");
        try {
            System.out.println("Method 1 execute");
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("Method 1 end");
    }

    public void method2() {
        System.out.println("Method 2 start");
        try {
            System.out.println("Method 2 execute");
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("Method 2 end");
    }

    public static void main(String[] args) {
        final SynchronizedTest test = new SynchronizedTest();
        new Thread(new Runnable() {
            @Override
            public void run() { test.method1(); }
        }).start();
        new Thread(new Runnable() {
            @Override
            public void run() { test.method2(); }
        }).start();
    }
}

Result: Both threads run concurrently; method2 finishes earlier because its sleep time is shorter.

2) Synchronizing an instance method

Code snippet 2 (the only change is the synchronized modifier on the methods):

package com.paddx.test.concurrent;

public class SynchronizedTest {
    public synchronized void method1() { /* same body as before */ }
    public synchronized void method2() { /* same body as before */ }
    // main method unchanged
}

Result: method2 must wait for method1 to complete because both synchronized methods lock the same object monitor.

3) Synchronizing static methods

Code snippet 3 (static synchronized methods):

package com.paddx.test.concurrent;

public class SynchronizedTest {
    public static synchronized void method1() { /* same body */ }
    public static synchronized void method2() { /* same body */ }
    public static void main(String[] args) {
        final SynchronizedTest test = new SynchronizedTest();
        final SynchronizedTest test2 = new SynchronizedTest();
        new Thread(() -> test.method1()).start();
        new Thread(() -> test2.method2()).start();
    }
}

Result: Even though test and test2 are different instances, static synchronization locks the Class object, so the methods execute sequentially.

4) Synchronizing a code block

Code snippet 4 (synchronized block on this inside ordinary methods):

package com.paddx.test.concurrent;

public class SynchronizedTest {
    public void method1() {
        System.out.println("Method 1 start");
        try {
            synchronized (this) {
                System.out.println("Method 1 execute");
                Thread.sleep(3000);
            }
        } catch (InterruptedException e) { e.printStackTrace(); }
        System.out.println("Method 1 end");
    }
    public void method2() {
        System.out.println("Method 2 start");
        try {
            synchronized (this) {
                System.out.println("Method 2 execute");
                Thread.sleep(1000);
            }
        } catch (InterruptedException e) { e.printStackTrace(); }
        System.out.println("Method 2 end");
    }
    // main method unchanged
}

Result: Thread 2 enters its method but must wait before entering the synchronized block because both blocks lock the same object monitor.

2. Underlying Mechanism of Synchronized

To understand the execution results, we look at the JVM bytecode. Decompiling a synchronized block yields the monitorenter and monitorexit instructions:

package com.paddx.test.concurrent;
public class SynchronizedDemo {
    public void method() {
        synchronized (this) {
            System.out.println("Method 1 start");
        }
    }
}

The JVM specification describes monitorenter as acquiring the monitor associated with an object. If the monitor’s entry count is zero, the thread becomes the owner; if the thread already owns it, the count increments; otherwise the thread blocks until the monitor becomes free.

monitorexit requires the executing thread to be the monitor’s owner, decrements the entry count, and releases the monitor when the count reaches zero, allowing blocked threads to attempt acquisition.

For synchronized methods, the compiler adds the ACC_SYNCHRONIZED flag to the method’s access flags. At runtime, the JVM checks this flag, acquires the appropriate monitor (object monitor for instance methods, class monitor for static methods), executes the method body, and then releases the monitor.

3. Explanation of Execution Results

With the monitor semantics in mind:

In snippet 2, both synchronized instance methods lock the same object monitor, so they run sequentially.

In snippet 3, static synchronized methods lock the class monitor, causing sequential execution even across different instances.

In snippet 4, synchronized blocks lock this , which is the same object for both methods, thus the blocks execute one after the other.

4. Summary

Synchronized is the most widely used tool for ensuring thread safety in Java. Understanding its underlying monitor mechanism helps developers use it correctly and choose more appropriate concurrency strategies when needed, leading to more robust multithreaded applications.

JavaJVMconcurrencymultithreadingsynchronizedMonitor
Selected Java Interview Questions
Written by

Selected Java Interview Questions

A professional Java tech channel sharing common knowledge to help developers fill gaps. Follow us!

0 followers
Reader feedback

How this landed with the community

login 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.