Java > Concurrency and Multithreading > Thread Basics > Thread Synchronization
Synchronized Block for Thread Safety
This snippet demonstrates thread synchronization using a synchronized block to protect critical sections of code from concurrent access. This ensures data consistency when multiple threads are modifying shared resources.
Code Snippet
This code defines a `SynchronizedCounter` class with a private `count` variable and a `lock` object. The `increment()` and `getCount()` methods use a `synchronized` block with the `lock` object to ensure that only one thread can access and modify the `count` variable at a time. The `main` method creates two threads that increment the counter, and the final count is printed after both threads have finished executing. Without the `synchronized` block, the final count would likely be incorrect due to race conditions.
public class SynchronizedCounter {
private int count = 0;
private final Object lock = new Object();
public void increment() {
synchronized (lock) {
count++;
}
}
public int getCount() {
synchronized (lock) {
return count;
}
}
public static void main(String[] args) throws InterruptedException {
SynchronizedCounter counter = new SynchronizedCounter();
Runnable task = () -> {
for (int i = 0; i < 10000; i++) {
counter.increment();
}
};
Thread thread1 = new Thread(task);
Thread thread2 = new Thread(task);
thread1.start();
thread2.start();
thread1.join();
thread2.join();
System.out.println("Final count: " + counter.getCount());
}
}
Concepts Behind the Snippet
Thread synchronization is crucial for preventing race conditions and ensuring data consistency when multiple threads access shared resources concurrently. The `synchronized` keyword in Java provides a mechanism for creating critical sections of code that can only be accessed by one thread at a time. This is achieved by associating a lock with each object. When a thread enters a `synchronized` block, it acquires the lock for the object. If another thread tries to enter a `synchronized` block for the same object, it will be blocked until the first thread releases the lock.
Real-Life Use Case
Imagine a banking application where multiple threads are trying to update the balance of a bank account concurrently. Without proper synchronization, it's possible for two threads to read the balance at the same time, perform different operations, and then write back incorrect values. This could lead to financial discrepancies and data corruption. Using synchronized blocks or methods ensures that only one thread can update the account balance at a time, preventing these issues.
Best Practices
Interview Tip
When discussing thread synchronization in interviews, be prepared to explain the concepts of race conditions, critical sections, and the different mechanisms for achieving synchronization in Java (e.g., `synchronized` keyword, `Lock` interface, `Semaphore`, `CountDownLatch`). Be able to describe the advantages and disadvantages of each approach and when to use them.
When to Use Them
Use synchronized blocks whenever multiple threads are accessing and modifying shared mutable state. If the data is immutable, synchronization may not be necessary. Also consider using other synchronization constructs like `ReentrantLock` or concurrent collections from `java.util.concurrent` for more advanced synchronization scenarios.
Memory Footprint
The memory footprint of synchronized blocks is relatively small. Each object in Java has an associated monitor, which is used for synchronization. The monitor adds a small overhead to the object's memory footprint. The main overhead comes from the potential blocking of threads waiting to acquire the lock, which can consume system resources. Also the dedicated lock object consume some memory too.
Alternatives
Alternatives to synchronized blocks include:
Pros
Cons
FAQ
-
What is a race condition?
A race condition occurs when multiple threads access and modify shared data concurrently, and the final outcome depends on the unpredictable order of execution. This can lead to data corruption and unexpected behavior. -
What is a critical section?
A critical section is a section of code that accesses shared resources and must be protected from concurrent access by multiple threads. Synchronized blocks are used to define critical sections. -
Why use a dedicated lock object instead of `synchronized(this)`?
Using a dedicated lock object provides more control and flexibility. It allows you to synchronize access to specific parts of your code without synchronizing the entire object. Also, it prevents external code from accidentally synchronizing on your object and interfering with your synchronization strategy.