Fundamentals 17 min read

Comprehensive Guide to Java synchronized Locks: Usage, Scenarios, and Best Practices

This article explains the Java synchronized keyword in depth, covering class-level and object-level locks, reentrancy, interruption behavior, timeout limitations, and performance tips, while providing numerous runnable code demos and practical conclusions for safe multithreaded programming.

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

This article provides a comprehensive guide to the Java synchronized keyword, illustrating its various lock types—class locks, object locks, and synchronized blocks—through concise demo programs and highlighting important usage considerations.

Synchronized class lock : When synchronized is applied to static methods or to synchronized(ClassName.class) blocks, all threads share a single lock associated with the class, regardless of the number of instances. The following demo shows two threads invoking different static methods of the same class; the class lock ensures mutual exclusion, while invoking an unsynchronized static method does not participate in the lock.

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...");
    }
}

The output confirms that both static synchronized methods block each other because they share the same class lock.

Synchronized class lock – variant 2 : Using synchronized on a static method in one class and on a synchronized(ClassName.class) block in another still shares the same lock if the class reference is identical. The demo below demonstrates this behavior.

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() {
        // 【Note】synchronized (SynchronizeAndClassLock2.class) will not be mutually exclusive
        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...");
        }
    }
}

When the synchronized block references the same class ( ClassLock2.class), the lock is effective; changing it to a different class removes the mutual exclusion.

Synchronized class lock – static object lock : Locking on a static object works similarly to class locking. The following example shows two threads synchronizing on the same static object, achieving mutual exclusion, while synchronizing on different static objects does not.

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();
}

Both threads synchronize on StaticLock2.staticLock, so they block each other; using different static objects would break the mutual exclusion.

Synchronized object lock : When synchronized is used on instance methods or on synchronized(this) blocks, the lock is tied to a specific object. The demos below illustrate that two threads operating on the same object are mutually exclusive, while threads operating on different objects are not.

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() {
        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 synchronized 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...");
    }
}

When the same instance is used, the two synchronized methods block each other; using separate instances removes the lock relationship.

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) {
            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 synchronized 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...");
    }
}

Here the synchronized method and the synchronized block on this share the same object lock, so they are mutually exclusive.

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() {
        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 synchronized 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...");
    }
}

Because each thread creates a different object, the two synchronized methods do not block each other.

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) {
            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 void test2() {
        synchronized (objectLock) {
            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...");
        }
    }
}

Both methods synchronize on the same objectLock, so they are mutually exclusive; using different lock objects would remove the exclusion.

Synchronized lock nuances :

Locks created by synchronized cannot be interrupted. The following dead‑lock demo shows that interrupting a thread does not release the lock.

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() + " and " + threadA.getName() + " will release lock1...");
        threadA.interrupt();
    }
}
synchronized

is re‑entrant: a thread that already holds a lock can acquire it again. Two demos illustrate re‑entrancy across different methods and within the same method (recursive call).

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...");
    }
}
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(); }
    }
}
synchronized

provides no timeout functionality, unlike ReentrantLock.tryLock, which can avoid deadlocks by giving up after a waiting period.

Methods wait() and notify() must be called inside a synchronized block; otherwise an IllegalMonitorStateException is thrown.

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

Performance tip: Prefer narrow synchronized blocks over method‑level locking. The Netty ServerBootstrap.childOption method synchronizes only the critical map updates, demonstrating a fine‑grained lock.

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;
}

In summary, the article systematically covers the various ways to use synchronized, the associated pitfalls such as non‑interruptibility and lack of timeout, re‑entrancy behavior, and best practices for minimizing lock scope to achieve better concurrency performance.

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.

Javalockingmultithreadingsynchronizedthread-safety
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.