Thread Synchronization
Thread synchronization in Java is the mechanism used to control access to shared resources in a multi-threaded environment. It ensures data consistency, thread safety, and predictable behavior when multiple threads execute concurrently. This is a very high-frequency interview topic.
Why Thread Synchronization Is Needed
In multithreading, multiple threads may access and modify shared data simultaneously, leading to:
- Race conditions
- Inconsistent results
- Data corruption
Example Problem (Race Condition)
class Counter {
int count = 0;
void increment() {
count++;
}
}
Multiple threads calling increment() → incorrect count
What Is Synchronization?
- Ensures only one thread accesses a critical section at a time
- Achieved using locks (monitors)
- Prevents race conditions
Synchronization in Java (Ways)
- Synchronized Method
- Synchronized Block
- Static Synchronization
- Inter-thread Communication (wait, notify) (preview)
1️⃣ Synchronized Method
Locks the object (instance).
class Counter {
int count = 0;
synchronized void increment() {
count++;
}
}
- ✔ Only one thread can execute this method per object
- ✔ Lock = this
2️⃣ Synchronized Block (Preferred)
Locks specific code, not entire method.
class Counter {
int count = 0;
void increment() {
synchronized (this) {
count++;
}
}
}
- ✔ Better performance
- ✔ Fine-grained control
3️⃣ Static Synchronization
Locks the class-level object.
class Counter {
static int count = 0;
static synchronized void increment() {
count++;
}
}
- ✔ Lock = Counter.class
- ✔ Shared across all objects
Object Lock vs Class Lock (Interview Favorite)
| Type | Lock Used |
|---|---|
| Instance method | Object lock (this) |
| Synchronized block | Specified object |
| Static method | Class lock (ClassName.class) |
What Is a Monitor Lock?
- Every Java object has an intrinsic lock (monitor)
synchronizedacquires this lock- Released when thread exits synchronized area
Synchronization Example (Correct Output)
class Counter {
int count = 0;
synchronized void increment() {
count++;
}
}
public class Test {
public static void main(String[] args) throws Exception {
Counter c = new Counter();
Thread t1 = new Thread(() -> {
for(int i=0;i<1000;i++) c.increment();
});
Thread t2 = new Thread(() -> {
for(int i=0;i<1000;i++) c.increment();
});
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println(c.count); // 2000
}
}
Synchronization and Performance
- Synchronization adds overhead
- Excessive synchronization reduces performance
- Use minimal critical sections
Synchronization Does NOT Solve
- Deadlocks
- Thread starvation
- Livelocks
- (Handled separately)
Alternatives to synchronized (Preview)
- java.util.concurrent.locks.Lock
- ReentrantLock
- Atomic classes (AtomicInteger)
Common Beginner Mistakes
- Synchronizing entire methods unnecessarily
- Synchronizing on wrong object
- Forgetting static vs instance lock difference
- Assuming synchronization fixes all concurrency issues
Interview-Ready Answers
Short Answer
Thread synchronization ensures that only one thread accesses shared resources at a time.
Detailed Answer
In Java, thread synchronization is achieved using the synchronized keyword to control access to critical sections of code. It prevents race conditions and ensures data consistency by allowing only one thread to hold a lock on an object or class at a time.
Thread Synchronization Examples (Interview Pack)
1. Race Condition (Without Synchronization)
class Counter {
int count = 0;
void increment() {
count++;
}
public static void main(String[] args) throws Exception {
Counter c = new Counter();
Thread t1 = new Thread(() -> {
for (int i = 0; i < 1000; i++) c.increment();
});
Thread t2 = new Thread(() -> {
for (int i = 0; i < 1000; i++) c.increment();
});
t1.start(); t2.start();
t1.join(); t2.join();
System.out.println(c.count);
}
}
Output: May be less than 2000
Explanation: Demonstrates race condition
2. Synchronized Method (Fixing Race Condition)
class Counter {
int count = 0;
synchronized void increment() {
count++;
}
public static void main(String[] args) throws Exception {
Counter c = new Counter();
Thread t1 = new Thread(() -> {
for (int i = 0; i < 1000; i++) c.increment();
});
Thread t2 = new Thread(() -> {
for (int i = 0; i < 1000; i++) c.increment();
});
t1.start(); t2.start();
t1.join(); t2.join();
System.out.println(c.count);
}
}
Output: 2000
3. Synchronized Block (Better Performance)
class Counter {
int count = 0;
void increment() {
synchronized (this) {
count++;
}
}
}
Explanation: Locks only critical section
Why: Better than synchronizing entire method
4. Object-Level Lock (Instance Synchronization)
class Task {
synchronized void work() {
System.out.println(Thread.currentThread().getName() + " working");
}
public static void main(String[] args) {
Task obj = new Task();
new Thread(obj::work).start();
new Thread(obj::work).start();
}
}
Explanation: Lock is on object instance
5. Two Objects → Two Locks (No Blocking)
class Task {
synchronized void work() {
System.out.println(Thread.currentThread().getName() + " working");
}
public static void main(String[] args) {
new Thread(() -> new Task().work()).start();
new Thread(() -> new Task().work()).start();
}
}
Explanation: Different objects → no synchronization conflict
6. Static Synchronized Method (Class-Level Lock)
class Printer {
static synchronized void print() {
System.out.println(Thread.currentThread().getName());
}
public static void main(String[] args) {
new Thread(Printer::print).start();
new Thread(Printer::print).start();
}
}
Explanation: Lock is on Class object
7. Object Lock vs Class Lock Difference
class Test {
synchronized void instanceMethod() {}
static synchronized void staticMethod() {}
}
Explanation: Instance method → object lock
Explanation: Static method → class lock
8. Synchronizing on Custom Lock Object
class Counter {
int count = 0;
final Object lock = new Object();
void increment() {
synchronized (lock) {
count++;
}
}
}
Explanation: Avoids locking this
Why: Better design
9. Synchronization with Runnable
class Task implements Runnable {
int count = 0;
public synchronized void run() {
count++;
}
}
10. Deadlock Example (Very Important)
class Deadlock {
static final Object A = new Object();
static final Object B = new Object();
public static void main(String[] args) {
Thread t1 = new Thread(() -> {
synchronized (A) {
synchronized (B) {
System.out.println("Thread 1");
}
}
});
Thread t2 = new Thread(() -> {
synchronized (B) {
synchronized (A) {
System.out.println("Thread 2");
}
}
});
t1.start();
t2.start();
}
}
Explanation: Both threads wait forever
Why: Classic deadlock
11. Avoiding Deadlock (Consistent Lock Order)
synchronized (A) {
synchronized (B) {
// safe
}
}
Rule: Always acquire locks in same order
12. sleep() Does NOT Release Lock
class Demo {
synchronized void work() throws Exception {
Thread.sleep(1000);
}
}
Explanation: Lock held during sleep
13. wait() Releases Lock
class Demo {
synchronized void work() throws Exception {
wait();
}
}
Explanation: Lock released until notify()
14. notify() and notifyAll()
class Demo {
synchronized void resume() {
notifyAll();
}
}
15. Synchronization Overhead (Concept)
// Over-synchronization reduces performance
Best Practice: Synchronize minimum code
16. Thread-Safe Increment Using AtomicInteger
import java.util.concurrent.atomic.AtomicInteger;
class Demo {
static AtomicInteger count = new AtomicInteger();
public static void main(String[] args) {
count.incrementAndGet();
System.out.println(count.get());
}
}
Explanation: No explicit synchronization
Why: Lock-free
17. Synchronized vs Non-Synchronized Methods
class Demo {
synchronized void syncMethod() {}
void normalMethod() {}
}
Explanation: Non-sync method runs concurrently
18. Synchronization with Multiple Methods
class Demo {
synchronized void m1() {}
synchronized void m2() {}
}
Explanation: Same lock → one thread at a time
19. Common Interview Trap
synchronized (new Object()) {
// useless lock
}
Explanation: New object every time → no real synchronization
20. Interview Summary – Thread Synchronization
synchronized
Key Points:
- Prevents race conditions
- Uses intrinsic locks
- Object lock vs Class lock
- Deadlock risk exists
- Synchronize minimal code
Key Takeaway
Synchronization protects shared data, not threads. Use it carefully and minimally to balance correctness and performance.