Fundamentals 19 min read

Comprehensive Guide to Java synchronized Locks: Usage, Scenarios, and Pitfalls

This article explains the various usages of Java's synchronized keyword, including class locks, object locks, static and instance methods, code blocks, and important considerations such as non‑interruptibility, reentrancy, lack of timeout, and best‑practice performance tips, illustrated with multiple demo programs.

Full-Stack Internet Architecture
Full-Stack Internet Architecture
Full-Stack Internet Architecture
Comprehensive Guide to Java synchronized Locks: Usage, Scenarios, and Pitfalls

1 Preface

This article uses simple demo programs to illustrate the different usages of the synchronized lock and related precautions, providing a reference for personal memory.

2 synchronized Class Lock

The synchronized lock is a JVM‑built‑in lock, different from ReentrantLock. The keyword can be applied to methods or code blocks, to static or non‑static members, and the scope varies accordingly.

2.1 synchronized on two static methods of the same class are mutually exclusive

public class SynchronizeAndClassLock {
    public static void main(String[] args) throws Exception {
        new Thread(() -> {
            // new a ClassLock object
            new ClassLock().test1();
        }).start();

        new Thread(() -> {
            // new another ClassLock object
            new ClassLock().test2();
        }).start();
    }
}

class ClassLock {
    public synchronized static void test1() {
        System.out.println(new Date() + " " + Thread.currentThread().getName() + " begin...");
        try { TimeUnit.SECONDS.sleep(1); } catch (Exception e) {}
        System.out.println(new Date() + " " + Thread.currentThread().getName() + " end...");
    }
    // Note: public static void test2() will not be mutually exclusive because it is not synchronized.
    public synchronized static void test2() {
        System.out.println(new Date() + " " + Thread.currentThread().getName() + " begin...");
        try { TimeUnit.SECONDS.sleep(1); } catch (Exception e) {}
        System.out.println(new Date() + " " + Thread.currentThread().getName() + " end...");
    }
}

Result: Two threads executing synchronized static methods on different objects still acquire the same class lock, so they are mutually exclusive. If a method is not synchronized, the two threads will not block each other.

2.2 synchronized on a static method and a static block (class lock) are mutually exclusive

public class SynchronizeAndClassLock2 {
    public static void main(String[] args) throws Exception {
        new Thread(() -> {
            new ClassLock2().test1();
        }).start();

        new Thread(() -> {
            new ClassLock2().test2();
        }).start();
    }
}

class ClassLock2 {
    public synchronized static void test1() {
        System.out.println(new Date() + " " + Thread.currentThread().getName() + " begin...");
        try { TimeUnit.SECONDS.sleep(1); } catch (Exception e) {}
        System.out.println(new Date() + " " + Thread.currentThread().getName() + " end...");
    }
    public static void test2() {
        // synchronized (SynchronizeAndClassLock2.class) will not be mutually exclusive with test1 because it locks a different class.
        synchronized (ClassLock2.class) {
            System.out.println(new Date() + " " + Thread.currentThread().getName() + " begin...");
            try { TimeUnit.SECONDS.sleep(1); } catch (Exception e) {}
            System.out.println(new Date() + " " + Thread.currentThread().getName() + " end...");
        }
    }
}

Result: A static method synchronized on the class and a static block synchronized on the same class lock each other; changing the lock to another class removes the mutual exclusion.

2.3 synchronized on the same static object are mutually exclusive

public class SynchronizeAndClassLock10 {
    public static void main(String[] args) throws Exception {
        new Thread(() -> {
            new RunObject1().test1();
        }).start();
        new Thread(() -> {
            new RunObject2().test2();
        }).start();
    }
}

class RunObject1 {
    public static void test1() {
        synchronized (StaticLock2.staticLock) {
            System.out.println(new Date() + " " + Thread.currentThread().getName() + " begin...");
            try { TimeUnit.SECONDS.sleep(1); } catch (Exception e) {}
            System.out.println(new Date() + " " + Thread.currentThread().getName() + " end...");
        }
    }
}

class RunObject2 {
    public static void test2() {
        synchronized (StaticLock2.staticLock) {
            System.out.println(new Date() + " " + Thread.currentThread().getName() + " begin...");
            try { TimeUnit.SECONDS.sleep(1); } catch (Exception e) {}
            System.out.println(new Date() + " " + Thread.currentThread().getName() + " end...");
        }
    }
}

class StaticLock2 {
    public static Object staticLock = new Object();
}

Result: When both methods synchronize on the same static object, they block each other; using different static objects removes the mutual exclusion.

3 synchronized Object Lock

The object lock works at the instance level; different instances do not block each other.

3.1 synchronized on two non‑static methods of the same object are mutually exclusive

public class SynchronizeAndObjectLock2 {
    public static void main(String[] args) throws Exception {
        SynchronizeAndObjectLock2 lock = new SynchronizeAndObjectLock2();
        new Thread(() -> lock.test1()).start();
        new Thread(() -> lock.test2()).start();
    }
    public synchronized void test1() { /* ... */ }
    public synchronized void test2() { /* ... */ }
}

Result: Two threads invoking synchronized methods on the same object acquire the same object lock and therefore execute sequentially.

3.2 synchronized on a method and on synchronized(this) are mutually exclusive

public class SynchronizeAndObjectLock3 {
    public static void main(String[] args) throws Exception {
        SynchronizeAndObjectLock3 lock = new SynchronizeAndObjectLock3();
        new Thread(() -> lock.test1()).start();
        new Thread(() -> lock.test2()).start();
    }
    public void test1() {
        synchronized (this) {
            // critical section
        }
    }
    public synchronized void test2() { /* ... */ }
}

Result: The synchronized method and the synchronized block on this share the same object lock, so they block each other.

3.3 synchronized on different objects does not block

public class SynchronizeAndObjectLock {
    public static void main(String[] args) throws Exception {
        new Thread(() -> new SynchronizeAndObjectLock().test1()).start();
        new Thread(() -> new SynchronizeAndObjectLock().test2()).start();
    }
    public synchronized void test1() { /* ... */ }
    public synchronized void test2() { /* ... */ }
}

Result: Because each thread creates a separate instance, the two synchronized methods lock different objects and therefore run concurrently.

3.4 synchronized code block on the same object is mutually exclusive

public class SynchronizeAndObjectLock5 {
    private Object objectLock = new Object();
    public static void main(String[] args) throws Exception {
        SynchronizeAndObjectLock5 lock = new SynchronizeAndObjectLock5();
        new Thread(() -> lock.test1()).start();
        new Thread(() -> lock.test2()).start();
    }
    public void test1() {
        synchronized (objectLock) { /* critical section */ }
    }
    public void test2() {
        synchronized (objectLock) { /* critical section */ }
    }
}

Result: Both methods synchronize on the same explicit lock object, so they block each other; using different lock objects would remove the blocking.

4 synchronized on class and on object are independent

public class ClassAndObjectLock {
    public static void main(String[] args) throws Exception {
        new Thread(() -> ClassAndObjectLock.test1()).start();
        new Thread(() -> new ClassAndObjectLock().test2()).start();
    }
    public static void test1() {
        synchronized (ClassAndObjectLock.class) { /* class lock */ }
    }
    public void test2() {
        synchronized (this) { /* object lock */ }
    }
}

Result: Class lock and object lock are independent; they do not interfere with each other.

5 synchronized Lock Considerations

5.1 synchronized lock cannot be interrupted

The demo creates a deadlock with two threads holding each other's locks; interrupting one thread from the main thread does not release the lock because synchronized is non‑interruptible.

public class DeadLockCannotInterruptDemo {
    private static Object lock1 = new Object();
    private static Object lock2 = new Object();
    public static void main(String[] args) throws Exception {
        Thread threadA = new Thread(() -> {
            synchronized (lock1) {
                System.out.println(Thread.currentThread().getName() + " get lock1");
                try { Thread.sleep(10); synchronized (lock2) { System.out.println(Thread.currentThread().getName() + " get lock2"); } }
                catch (InterruptedException e) { e.printStackTrace(); }
            }
        });
        Thread threadB = new Thread(() -> {
            synchronized (lock2) {
                System.out.println(Thread.currentThread().getName() + " get lock2");
                try { Thread.sleep(10); synchronized (lock1) { System.out.println(Thread.currentThread().getName() + " get lock1"); } }
                catch (InterruptedException e) { e.printStackTrace(); }
            }
        });
        threadA.start();
        threadB.start();
        TimeUnit.SECONDS.sleep(3);
        System.out.println("main thread begin to interrupt " + threadA.getName());
        threadA.interrupt();
    }
}

5.2 synchronized is re‑entrant

5.2.1 Different methods can call each other

public class SynchronizeAndReentrant {
    public static void main(String[] args) throws Exception {
        SynchronizeAndReentrant obj = new SynchronizeAndReentrant();
        obj.test1();
    }
    public synchronized void test1() {
        System.out.println("test1 method is called...");
        test2();
    }
    public synchronized void test2() {
        System.out.println("test2 method is called...");
    }
}

5.2.2 Same method can call itself recursively

public class SynchronizeAndReentrant2 {
    int i = 1;
    public static void main(String[] args) throws Exception {
        SynchronizeAndReentrant2 obj = new SynchronizeAndReentrant2();
        obj.test1();
    }
    public synchronized void test1() {
        System.out.println("test1 method is called " + i++ + "st time...");
        while (i < 5) { test1(); }
    }
}

5.3 synchronized does not support timeout

Unlike ReentrantLock.tryLock, the built‑in synchronized keyword cannot specify a waiting timeout, which may increase the risk of deadlock.

5.4 wait/notify must be used inside a synchronized block

public class NotifyNeedSynchronized {
    public static Object lock = new Object();
    public static void main(String[] args) throws Exception {
        // lock.notify(); // would throw IllegalMonitorStateException
        lock.wait(); // also throws IllegalMonitorStateException unless inside synchronized
    }
}

Calling notify or wait on an object without holding its monitor results in IllegalMonitorStateException.

5.5 Keep synchronized scope as small as possible for performance

Prefer synchronizing only the critical section rather than the whole method. Example from Netty source code shows synchronized blocks around a shared map instead of the whole method.

public <T> ServerBootstrap childOption(ChannelOption<T> childOption, T value) {
    if (childOption == null) { throw new NullPointerException("childOption"); }
    if (value == null) {
        synchronized (childOptions) { childOptions.remove(childOption); }
    } else {
        synchronized (childOptions) { childOptions.put(childOption, value); }
    }
    return this;
}

6 Summary

The article reviews the various usages and precautions of the synchronized keyword through concise demos, and will later explore its underlying implementation.

Follow the "Source Code Notes" public account on WeChat. GitHub repository: https://github.com/yuanmabiji/Java-SourceCode-Blogs

Recommended Reading:

I wrote a singleton with DCL, but the interviewer was not satisfied!

What is a database "buffer pool"? (Comprehensive guide)

Six questions why Kafka is so powerful!

A single space caused a "disaster"

"Pitfall" ranking: Top 10 most counter‑intuitive Java features

Explosive! MySQL in 82 pictures

Interview tip: Docker fundamentals explained

Shunfeng Express: Please accept the MySQL soul ten‑pack

Welcome to follow the WeChat public account "Internet Full‑Stack Architecture" for more valuable information.

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.

Javalockingmultithreadingsynchronized
Full-Stack Internet Architecture
Written by

Full-Stack Internet Architecture

Introducing full-stack Internet architecture technologies centered on Java

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.