In Java, you can create a synchronization class using various mechanisms to ensure that multiple threads can safely access shared resources without causing inconsistencies. The most common ways to achieve synchronization in Java include:
synchronized
keyword: To create synchronized methods or synchronized blocks.- Reentrant Locks (
java.util.concurrent.locks.Lock
): Provides more advanced synchronization options thansynchronized
.
Let’s create an example of a synchronized class using both the synchronized
keyword and the ReentrantLock.
1. Synchronized Class Using the synchronized
Keyword
public class SynchronizedCounter {
private int count = 0;
// Synchronized method to increment the count
public synchronized void increment() {
count++;
}
// Synchronized method to decrement the count
public synchronized void decrement() {
count--;
}
// Synchronized method to get the current count
public synchronized int getCount() {
return count;
}
public static void main(String[] args) throws InterruptedException {
SynchronizedCounter counter = new SynchronizedCounter();
// Creating two threads that will increment and decrement the counter
Thread incrementThread = new Thread(() -> {
for (int i = 0; i < 1000; i++) {
counter.increment();
}
});
Thread decrementThread = new Thread(() -> {
for (int i = 0; i < 1000; i++) {
counter.decrement();
}
});
// Start both threads
incrementThread.start();
decrementThread.start();
// Wait for both threads to finish
incrementThread.join();
decrementThread.join();
// Get the final count (should be 0 if synchronized properly)
System.out.println("Final Count: " + counter.getCount());
}
}
Explanation:
- The
SynchronizedCounter
class uses thesynchronized
keyword on methods to ensure that only one thread can execute these methods at a time. - Two threads,
incrementThread
anddecrementThread
, modify the sharedcount
variable. Because of synchronization, thecount
remains consistent, and race conditions are avoided.
2. Synchronized Class Using ReentrantLock
ReentrantLock
provides more flexibility than the synchronized
keyword. For instance, you can try to acquire a lock without blocking, set timeouts, and more.
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class ReentrantLockCounter {
private int count = 0;
private final Lock lock = new ReentrantLock();
// Method to increment the count using ReentrantLock
public void increment() {
lock.lock();
try {
count++;
} finally {
lock.unlock(); // Ensure that the lock is released
}
}
// Method to decrement the count using ReentrantLock
public void decrement() {
lock.lock();
try {
count--;
} finally {
lock.unlock();
}
}
// Method to get the current count
public int getCount() {
lock.lock();
try {
return count;
} finally {
lock.unlock();
}
}
public static void main(String[] args) throws InterruptedException {
ReentrantLockCounter counter = new ReentrantLockCounter();
// Creating two threads that will increment and decrement the counter
Thread incrementThread = new Thread(() -> {
for (int i = 0; i < 1000; i++) {
counter.increment();
}
});
Thread decrementThread = new Thread(() -> {
for (int i = 0; i < 1000; i++) {
counter.decrement();
}
});
// Start both threads
incrementThread.start();
decrementThread.start();
// Wait for both threads to finish
incrementThread.join();
decrementThread.join();
// Get the final count (should be 0 if synchronized properly)
System.out.println("Final Count: " + counter.getCount());
}
}
Explanation:
- Lock: The
ReentrantLock
provides more control over thread synchronization thansynchronized
. You need to manually lock and unlock it, ensuring that theunlock()
method is always called, typically in afinally
block. lock.lock()
: Acquires the lock. Only one thread can hold the lock at a time.lock.unlock()
: Releases the lock, allowing other threads to acquire it.
Key Differences Between synchronized
and ReentrantLock
:
- Explicit Locking: In
ReentrantLock
, locking and unlocking are done explicitly, whereas withsynchronized
, it’s implicit. - Timeouts:
ReentrantLock
allows the thread to try acquiring a lock for a specified time period. - Fairness:
ReentrantLock
can be configured to be fair, ensuring that threads acquire the lock in the order they requested it. - Interruptibility:
ReentrantLock
can be interrupted while waiting for the lock, whereas a thread waiting for asynchronized
block cannot be interrupted.
Summary:
synchronized
is simpler and built-in, but with less flexibility.ReentrantLock
is more flexible but requires more boilerplate code (manual locking and unlocking).
Both approaches provide safe access to shared resources and prevent race conditions in a multi-threaded environment. Choose the one that best fits your application’s complexity and requirements.