Overview:
Lesson 2 Module 7: Multithreading-Synchronization focuses on ensuring thread safety in Java through synchronization. When multiple threads access shared resources, there is a need to synchronize their access to prevent data corruption or inconsistent results. This lesson covers synchronized methods and blocks, essential mechanisms for controlling access to critical sections in a multithreaded environment.
Key Concepts:
- Thread Safety:
- Definition: Thread safety ensures that a program behaves correctly when multiple threads are executing concurrently.
- Purpose: Prevents data corruption and ensures consistent results when multiple threads access shared resources.
- Synchronized Methods:
- Definition: Synchronized methods are methods that are controlled by locks and can only be accessed by one thread at a time.
- Usage: Use the
synchronized
keyword in the method signature to make the entire method synchronized.
- Synchronized Blocks:
- Definition: Synchronized blocks are code blocks that are controlled by locks and can only be executed by one thread at a time.
- Usage: Use the
synchronized
keyword followed by an object reference (lock) within a code block to synchronize that block.
- Intrinsic Locks and Reentrancy:
- Intrinsic Locks (Monitors):
- In Java, every object has an intrinsic lock associated with it. Synchronized methods and blocks use this lock.
- Reentrancy:
- A thread that already holds a lock can acquire it again without blocking.
- Intrinsic Locks (Monitors):
Example:
Let’s create a program that demonstrates the need for synchronization and uses both synchronized methods and blocks to ensure thread safety.
class Counter {
private int count = 0;
// Synchronized method
public synchronized void increment() {
for (int i = 0; i < 5; i++) {
count++;
System.out.println(Thread.currentThread().getName() + " - Incremented Count: " + count);
try {
Thread.sleep(100); // Simulate some work
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
// Synchronized block
public void decrement() {
synchronized (this) {
for (int i = 0; i < 5; i++) {
count--;
System.out.println(Thread.currentThread().getName() + " - Decremented Count: " + count);
try {
Thread.sleep(100); // Simulate some work
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
public class SynchronizationExample {
public static void main(String[] args) {
Counter counter = new Counter();
// Creating threads
Thread incrementThread = new Thread(() -> counter.increment());
Thread decrementThread = new Thread(() -> counter.decrement());
// Starting threads
incrementThread.start();
decrementThread.start();
// Waiting for threads to complete
try {
incrementThread.join();
decrementThread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Main Thread - Execution Completed");
}
}
- Counter Class:
Counter
class contains a privatecount
variable that will be manipulated by multiple threads.increment()
method is synchronized using thesynchronized
keyword in the method signature, ensuring only one thread can execute this method at a time.decrement()
method uses a synchronized block withsynchronized (this)
to achieve the same effect.
- SynchronizationExample Class:
- In the
main
method, an instance of theCounter
class is created. - Two threads (
incrementThread
anddecrementThread
) are created with lambda expressions to execute theincrement()
anddecrement()
methods, respectively. - Both threads are started concurrently using the
start()
method.
- In the
- Thread Execution:
- Both threads execute their respective methods concurrently, and due to synchronization, they take turns accessing the shared
count
variable, preventing race conditions.
- Both threads execute their respective methods concurrently, and due to synchronization, they take turns accessing the shared
- Joining Threads:
- The
join()
method is used to wait for both threads to complete their execution before moving on with the main thread.
- The
- Output:
- The program outputs the incremented and decremented counts, demonstrating the synchronization of access to the shared variable.
Understanding the output helps visualize how synchronization ensures that the shared resource (count
variable) is accessed in a thread-safe manner, preventing data corruption.