Difference between Synchronised and ReentrantLock in Java Threading

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:

  1. synchronized keyword: To create synchronized methods or synchronized blocks.
  2. Reentrant Locks (java.util.concurrent.locks.Lock): Provides more advanced synchronization options than synchronized.

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 the synchronized keyword on methods to ensure that only one thread can execute these methods at a time.
  • Two threads, incrementThread and decrementThread, modify the shared count variable. Because of synchronization, the count 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 than synchronized. You need to manually lock and unlock it, ensuring that the unlock() method is always called, typically in a finally 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 with synchronized, 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 a synchronized 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.

Related Posts

Leave a Reply

Your email address will not be published. Required fields are marked *