Synchronized and ReentrantLock in Java

Synchronized and ReentrantLock in Java

In Java, both synchronized blocks/methods and ReentrantLock provide mechanisms for managing concurrent access to shared resources and ensuring thread safety. However, they differ in their features, flexibility, and usage.

Synchronized and ReentrantLock

Synchronized

  • Synchronized is a built-in Java keyword used to acquire and release locks for mutual exclusion.
  • It provides inherent locking mechanism and is easy to use, as it automatically acquires and releases locks.
  • Synchronization in Java is reentrant by default, meaning a thread can acquire the same lock multiple times without deadlocking itself (nested synchronized blocks).
  • It is more suitable for simple synchronization requirements and straightforward code blocks/methods.

ReentrantLock

  • ReentrantLock is a class in the java.util.concurrent.locks package that provides a flexible and explicit locking mechanism.
  • It offers additional features compared to synchronized, such as support for interruptible locking, timed locking, and fairness policies.
  • Unlike synchronized, ReentrantLock requires explicit acquisition and release of locks using lock() and unlock() methods, respectively.
  • ReentrantLock allows more fine-grained control over locking, such as acquiring multiple locks, using tryLock() for non-blocking lock acquisition, and implementing custom locking strategies.
  • It is more suitable for complex synchronization scenarios where fine-grained control over locking behavior is required.

Let’s demonstrate the difference between synchronized and ReentrantLock in Java with an example:

Example
java
import java.util.concurrent.locks.ReentrantLock;

public class SynchronizedVsReentrantLockExample {
    private static int count = 0;
    private static final Object lock = new Object();
    private static final ReentrantLock reentrantLock = new ReentrantLock();

    // Synchronized method
    public synchronized static void synchronizedMethod() {
        count++;
    }

    // ReentrantLock method
    public static void reentrantLockMethod() {
        reentrantLock.lock();
        try {
            count++;
        } finally {
            reentrantLock.unlock();
        }
    }

    public static void main(String[] args) throws InterruptedException {
        // Using synchronized
        Thread thread1 = new Thread(() -> {
            for (int i = 0; i < 10000; i++) {
                synchronizedMethod();
            }
        });

        // Using ReentrantLock
        Thread thread2 = new Thread(() -> {
            for (int i = 0; i < 10000; i++) {
                reentrantLockMethod();
            }
        });

        thread1.start();
        thread2.start();

        thread1.join();
        thread2.join();

        System.out.println("Using synchronized: " + count); // Expected: 20000
        System.out.println("Using ReentrantLock: " + count); // Expected: 20000
    }
}

Example

In this example, we have a shared variable count that we increment within synchronized and ReentrantLock methods. Both methods achieve mutual exclusion and thread safety, ensuring that count is incremented correctly. While synchronizedMethod() uses the intrinsic locking mechanism provided by synchronized, reentrantLockMethod() uses ReentrantLock for explicit locking. Both approaches yield the same result, demonstrating the difference in usage between synchronized and ReentrantLock.

Homepage

Readmore