Tuesday, July 25, 2023

Difference between ReentrantLock vs synchronized lock in Java? Example Tutorial

In concurrent programming, synchronization is essential to ensure that multiple threads can safely access shared resources without causing data inconsistencies or race conditions. In Java, there are two primary mechanisms for achieving synchronization: ReentrantLock and the synchronized keyword.  The ReentrantLock and synchronized lock both serve the purpose of allowing exclusive access to critical sections of code, but they differ in terms of flexibility, performance, and the level of control they provide to developers. Understanding the nuances between these two synchronization approaches is crucial for Java developers aiming to build efficient and reliable concurrent applications.

In this article , we will explore the key distinctions between ReentrantLock and the synchronized lock in Java. We will delve into their usage, advantages, limitations, and recommended scenarios for each to help you make informed decisions when employing synchronization mechanisms in your Java applications.

Let's dive into the comparison of ReentrantLock and synchronized to gain insights into which approach best suits your concurrent programming needs.

Difference between ReentrantLock vs synchronized lock in Java?

Here are key differences between a ReentrantLock and synchronized keyword lock in Java for easy reference:

1. The lock acquired by synchronized block or method cannot go beyond a single method. Thread has to leave the lock one it goes out of method, but Lock interface or ReentrantLock allows you to extend locking scope beyond one method. It's possible to call lock() and unlock() in separate methods.

2. ReentrantLock is more flexible than synchronized lock in Java. All other threads trying to get lock is blocked if synchronized lock is not available but ReentrantLock provides a method called tryLock() which can check if lock is available or not without blocking. This method return false if lock is not available and return immediately without blocking. You can reduce chance of deadlock by using this method.

ReentrantLock vs synchronized lock



3. In case of synchronized block and method, only one thread can access shared resource, but with new lock interface its possible to have shared locks e.g. ReadLock from ReadWriteReentrantLock. Many readers can access shared resource simultaneously by using ReadLock in Java.


4. In case of synchronized lock, JVM is responsible for acquiring and releasing lock, but in case of ReentrantLock, its programmer's responsibility to acquire and release lock. 

Here is the idiom to use ReentrantLock in Java.




5. Due to above difference, working with synchronized lock is less error prone than working with ReentrantLock. Since lock is release automatically even in case of exception, it's even safe for beginners to write code using synchronized block, but you need some more care while using lock interface and ReentrantLock.  You must follow the standard idiom shown above and must remember to release lock in finally block, so that lock is release under all conditions.


6. Now, let' checkout ordering part as this is one of the biggest headache on acquiring and releasing lock. synchronized keyword makes no guarantee on which thread will acquire the lock. For example, if 4 threads are waiting for lock then any of them can acquire the lock and start execution regardless of the order they entered into waiting queue. On the other hand ReentrantLock maintains sequence by using a fairness boolean



ReentrantLock vs synchronized keyword - An example

Now, Let's see an example to illustrate the difference between ReentrantLock and the synchronized lock in Java.

Example Scenario: Bank Account Balance Update

Consider a simple scenario where multiple threads attempt to update a shared bank account balance concurrently. To maintain data integrity, we need to ensure that only one thread can update the balance at a time.

Using synchronized Block:

public class BankAccount {
    private double balance;
    private final Object lock = new Object();

    public void deposit(double amount) {
        synchronized (lock) {
            balance += amount;
        }
    }

    public void withdraw(double amount) {
        synchronized (lock) {
            balance -= amount;
        }
    }
}

In this example, we use the synchronized keyword with a lock object (lock) to achieve mutual exclusion. The lock ensures that only one thread can execute the deposit or withdraw methods at any given time, preventing potential data race issues.

Using ReentrantLock:

import java.util.concurrent.locks.*;

public class BankAccount {
    private double balance;
    private final ReentrantLock lock = new ReentrantLock();

    public void deposit(double amount) {
        lock.lock();
        try {
            balance += amount;
        } finally {
            lock.unlock();
        }
    }

    public void withdraw(double amount) {
        lock.lock();
        try {
            balance -= amount;
        } finally {
            lock.unlock();
        }
    }
}

In this version, I have used ReentrantLock to achieve the same synchronization as before. The lock object is acquired using lock.lock(), and the critical section is protected using a try-finally block to ensure the lock is released even if an exception occurs.

Key Differences:

Now, that we have seen the example and also know the difference between ReentrantLock and sycnhronzied lock, let's find those differences in this example:

1. Flexibility
ReentrantLock offers more flexibility compared to the synchronized block. For example, it allows you to try to acquire the lock with a timeout (tryLock()) or attempt to acquire the lock interruptibly (lockInterruptibly()), which can be useful in certain situations.

2. Readability
The synchronized block uses the familiar synchronized keyword, which can make the code more readable. ReentrantLock code may be a bit more verbose due to explicit lock acquisition and release.

3. Performance
In some cases, ReentrantLock can provide better performance than synchronized due to its improved handling of contention. However, the actual performance gain varies depending on the use case and JVM implementation.

4. Nested Locking
ReentrantLock supports nested locking, which means a thread can acquire the same lock multiple times (as long as it releases it an equal number of times). This is not possible with synchronized, as it automatically re-entrant.

In conclusion, both ReentrantLock and synchronized can be used to achieve synchronization in Java. ReentrantLock offers more advanced features and flexibility, but it might come with additional complexity. On the other hand, synchronized is straightforward and easier to use but lacks some of the features offered by ReentrantLock. Your choice between the two should be based on your specific requirements and performance considerations.

Related Java Concurrency Articles you may like
  • 5 Courses to Learn Java Multithreading in-depth (courses)
  • Difference between CyclicBarrier and CountDownLatch in Java? (answer)
  • How does Exchanger works in Java Multithreading (tutorial)
  • 21 Tech Skills Java Developer can learn (skills)
  • Difference between Thread and Executor in Java? (answer)
  • 10 Tips to become a better Java Developer (tips)
  • 10 Best courses to learn Java in depth (courses)
  • Understanding the flow of data and code in Java program (answer)
  • How to avoid deadlock in Java? (answer)
  • How to do inter-thread communication in Java using wait-notify? (answer)
  • 5 Essential Skills to Crack Java Interviews (skills)
  • Top 5 Books to Master Concurrency in Java (books)
  • Top 50 Multithreading and Concurrency Questions in Java (questions)
  • 10 Advanced Java Courses for Experienced Devs (courses)

Thanks for reading this article so far. If you find this article useful then please share with your friends and colleagues. If you have any questions or feedback then please drop a note.

No comments:

Post a Comment

Feel free to comment, ask questions if you have any doubt.