Why Your synchronized Block Fails: A Real‑World Java Thread‑Safety Case Study
A Java multithreading example shows that synchronizing on a mutable Integer field does not guarantee exclusive access, and the article explains the root cause, demonstrates correct locking techniques, and offers best‑practice tips for safe concurrent programming.
Problematic Code
The article starts with a stripped‑down Java class SyncTest that implements Runnable. Two threads share a single SyncTest instance and increment a non‑final Integer count inside a synchronized(count) block, then sleep for 10 seconds.
public class SyncTest implements Runnable {
private Integer count = 0;
@Override
public void run() {
synchronized (count) {
System.out.println(new Date() + " 开始休眠" + Thread.currentThread().getName());
count++;
try {
Thread.sleep(10000);
System.out.println(new Date() + " 结束休眠" + Thread.currentThread().getName());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) throws InterruptedException {
SyncTest test = new SyncTest();
new Thread(test).start();
Thread.sleep(100);
new Thread(test).start();
}
}Running the program prints both threads’ "开始休眠" messages almost simultaneously, proving that the lock did not work.
Execution Result
Fri Jul 23 22:10:34 CST 2021 开始休眠Thread-0
Fri Jul 23 22:10:34 CST 2021 开始休眠Thread-1
Fri Jul 23 22:10:44 CST 2021 结束休眠Thread-0
Fri Jul 23 22:10:45 CST 2021 结束休眠Thread-1The expectation was that the first thread would finish its critical section before the second entered, but the logs show concurrent entry.
synchronized Overview
The synchronized keyword can be applied in three ways:
Synchronize an instance method – locks the current object.
Synchronize a static method – locks the Class object.
Synchronize a block – locks the object specified in parentheses.
In the example the third form is used. The JVM inserts monitor.enter and monitor.exit bytecode instructions to acquire and release the monitor of the lock object.
Root Cause Analysis
IDE inspections warn: "Synchronization on a non‑final field 'count'" because the lock expression refers to a mutable object. Each thread may lock a different Integer instance (autoboxing creates a new object after count++), so the monitors are distinct and the lock is ineffective.
Synchronization on a non‑final field 'xxx' Inspection info: Reports synchronized statements where the lock expression is a reference to a non‑final field. Such statements are unlikely to have useful semantics, as different threads may be locking on different objects even when operating on the same object.
Corrected Implementation
Locking on the enclosing object ( this) ensures all threads synchronize on the same monitor:
public class SyncTest implements Runnable {
private Integer count = 0;
@Override
public void run() {
synchronized (this) {
System.out.println(new Date() + " 开始休眠" + Thread.currentThread().getName());
count++;
try {
Thread.sleep(10000);
System.out.println(new Date() + " 结束休眠" + Thread.currentThread().getName());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
// ...
}Running the corrected program yields:
Fri Jul 23 23:13:55 CST 2021 开始休眠Thread-0
Fri Jul 23 23:14:05 CST 2021 结束休眠Thread-0
Fri Jul 23 23:14:05 CST 2021 开始休眠Thread-1
Fri Jul 23 23:14:15 CST 2021 结束休眠Thread-1Now the second thread starts only after the first releases the lock.
Alternative: Dedicated Lock Object
Locking on this can cause unnecessary contention if other synchronized methods also use the same monitor. A common pattern is to create a private final lock object:
public class SyncTest implements Runnable {
private Integer count = 0;
private final Object locker = new Object();
@Override
public void run() {
synchronized (locker) {
System.out.println(new Date() + " 开始休眠" + Thread.currentThread().getName());
count++;
try {
Thread.sleep(10000);
System.out.println(new Date() + " 结束休眠" + Thread.currentThread().getName());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}Practical Tips for Using synchronized
Prefer private lock objects; expose them via getters only as immutable copies.
If the locked object is a mutable collection, synchronize the getter and return a defensive copy.
The lock is always an object, never a code fragment; different synchronized blocks on different objects do not coordinate.
Each object has a single associated monitor.
Excessive synchronization adds overhead and can lead to deadlocks—use it judiciously.
Conclusion
The case study highlights two key takeaways: never ignore IDE warnings about synchronization on mutable fields, and always ensure all threads lock on the same, preferably immutable, monitor object to achieve true thread safety.
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.
Senior Brother's Insights
A public account focused on workplace, career growth, team management, and self-improvement. The author is the writer of books including 'SpringBoot Technology Insider' and 'Drools 8 Rule Engine: Core Technology and Practice'.
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.
