Thread Synchronization
In Java, when multiple threads access and modify shared data simultaneously, it can lead to race conditions and inconsistent results. Thread synchronization is the process of coordinating the access to shared data between multiple threads to ensure data integrity and prevent race conditions.
Why do we need thread synchronization?
We need thread synchronization in scenarios where multiple threads are accessing and modifying shared data concurrently. Without synchronization, the operations performed by multiple threads may interfere with each other, leading to unexpected and incorrect results. By synchronizing the access to shared data, we can ensure that only one thread can access the data at a time, avoiding race conditions and maintaining data consistency.
Synchronization techniques in Java
Java provides several synchronization techniques to coordinate the access to shared data:
Synchronized methods
: Methods can be declared as synchronized to ensure that only one thread can execute the method at a time. The synchronized keyword acquires an exclusive lock on the object's monitor when the method is called, preventing other threads from executing the method concurrently.Synchronized blocks
: Specific blocks of code can be enclosed within a synchronized block to achieve synchronization. The synchronized block acquires the lock on the specified object's monitor before executing the enclosed code, ensuring that only one thread can execute the code block at a time.Volatile variables
: The volatile keyword can be used to declare variables that should be accessed by multiple threads. When a variable is declared as volatile, it ensures that reads and writes to the variable are atomic, guaranteeing visibility of the variable's value to all threads.Locks
: The Lock interface and its implementations, such as ReentrantLock, provide more fine-grained control over thread synchronization. Locks allow for advanced features like fairness, condition variables, and interruptible locks.
Each synchronization technique has its advantages and use cases. The choice of synchronization technique depends on the specific requirements and complexity of the concurrent code.
Let's take a look at an example that demonstrates thread synchronization using a synchronized block:
1// Create a shared counter
2class Counter {
3 private int value = 0;
4
5 public void increment() {
6 // Synchronize access to the shared variable
7 synchronized (this) {
8 value++;
9 }
10 }
11
12 public int getValue() {
13 return value;
14 }
15}
16
17// Create an increment thread
18class IncrementThread implements Runnable {
19 private Counter counter;
20
21 public IncrementThread(Counter counter) {
22 this.counter = counter;
23 }
24
25 public void run() {
26 for (int i = 0; i < 10000; i++) {
27 counter.increment();
28 }
29 }
30}
31
32// Usage example
33Counter counter = new Counter();
34
35// Create multiple threads
36Thread thread1 = new Thread(new IncrementThread(counter));
37Thread thread2 = new Thread(new IncrementThread(counter));
38
39// Start the threads
40thread1.start();
41thread2.start();
42
43// Print the final counter value
44System.out.println("Final Counter Value: " + counter.getValue());
xxxxxxxxxx
}
class Main {
public static void main(String[] args) {
// Synchronization using synchronized block
Counter counter = new Counter();
// Create multiple threads
Thread thread1 = new Thread(new IncrementThread(counter));
Thread thread2 = new Thread(new IncrementThread(counter));
// Start the threads
thread1.start();
thread2.start();
// Wait for threads to finish
try {
thread1.join();
thread2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
// Print the final counter value
System.out.println("Final Counter Value: " + counter.getValue());
}
}
class Counter {
private int value = 0;