When multiple threads need to update the same data structure, proper synchronization is required to prevent race conditions, data corruption, and inconsistent results. Java provides several concurrency mechanisms such as synchronized blocks, locks, concurrent collections, and atomic classes to ensure thread-safe access to shared resources.
Key Points: • Multiple threads updating shared data can cause race conditions. • Synchronization ensures that only one thread modifies critical data at a time. • Java provides synchronized, Lock, Atomic classes, and concurrent collections for thread safety. • Choosing the right synchronization mechanism depends on performance and scalability requirements. • Thread safety is essential in multi-threaded applications such as banking, e-commerce, and web servers.
What Problem Can Occur?
Suppose two threads update the same bank account balance.
Initial Balance:
1000
Thread 1:
Deposit 500
Thread 2:
Withdraw 200
Without synchronization:
• Both threads may read the same balance simultaneously. • Updates may overwrite each other. • Final balance may become incorrect.
This situation is known as a Race Condition.
Solution 1: Using synchronized
The synchronized keyword ensures that only one thread can execute the critical section at a time.
Code Example:
class Counter {
private int count = 0;
public synchronized void increment() {
count++;
}
public int getCount() {
return count;
}
}Benefits:
• Simple to implement. • Prevents concurrent modifications. • Suitable for most shared data scenarios.
Solution 2: Using Synchronized Block
Instead of locking an entire method, only the critical section is locked.
Example:
public void increment() {
synchronized (this) {
count++;
}
}Benefits:
• Better performance. • Smaller locked area. • Reduced thread contention.
Solution 3: Using ReentrantLock
Provides more control than synchronized.
Example:
private final ReentrantLock lock =
new ReentrantLock();
public void increment() {
lock.lock();
try {
count++;
} finally {
lock.unlock();
}
}Benefits:
• Explicit lock management. • Supports fairness policies. • Advanced locking features.
Solution 4: Using Concurrent Collections
For collections shared by multiple threads, Java provides thread-safe alternatives.
Examples:
• ConcurrentHashMap • CopyOnWriteArrayList • ConcurrentLinkedQueue
Example:
Map<Integer, String> users =
new ConcurrentHashMap<>();Benefits:
• High concurrency. • Better performance than synchronizing entire collections. • Designed specifically for multi-threaded access.
Solution 5: Using Atomic Classes
For simple numeric updates, Atomic classes are efficient.
Example:
AtomicInteger counter =
new AtomicInteger(0);
counter.incrementAndGet();Benefits:
• Lock-free operations. • Excellent performance. • Suitable for counters and statistics.
Example: Suppose an e-commerce application maintains product inventory.
Multiple threads:
• Customer A places an order. • Customer B places an order. • Admin updates stock.
Without synchronization:
Inventory count may become incorrect.
With synchronization:
Inventory updates remain consistent and accurate.
Code Example:
class Inventory {
private int stock = 100;
public synchronized void purchase() {
stock--;
}
public int getStock() {
return stock;
}
}
public class Demo {
public static void main(String[] args) {
Inventory inventory =
new Inventory();
Runnable task = () -> {
for (int i = 0; i < 10; i++) {
inventory.purchase();
}
};
new Thread(task).start();
new Thread(task).start();
}
}Best Approach by Scenario
Simple Shared Variable:
• synchronized • AtomicInteger
Shared Collection:
• ConcurrentHashMap • CopyOnWriteArrayList
Complex Locking Requirements:
• ReentrantLock
High-Concurrency Applications:
• Concurrent Collections • Atomic Classes
Real-World Examples
• Bank account transactions • Inventory management systems • Online booking systems • Payment processing applications • Session management in web servers
Interview Tip: A concise interview answer is:
"When two threads need to update the same data structure, thread safety must be ensured to avoid race conditions. This can be achieved using synchronized methods or blocks, ReentrantLock, Atomic classes, or concurrent collections such as ConcurrentHashMap. The choice depends on the complexity of the operation and the application's performance requirements."