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.
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();
}
} synchronizedis 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(); }
}
} synchronizedprovides 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.
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.
Full-Stack Internet Architecture
Introducing full-stack Internet architecture technologies centered on Java
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.
