Java Semaphore vs ReentrantLock

Learn what a Semaphore and a Reentrant Lock are along with practical examples. We will also explore some of the main differences between the two and the use cases where we can use these while working in a multithreaded application.

1. What is a Semaphore?

A Semaphore is a Thread synchronization construct that acts as a lock with counter functionality. Semaphore class present in java.util.concurrent package, implements Serializable Interface and has been there since Java version 1.5.

Conceptually, a Semaphore maintains a set of permits represented by a counter value that can be incremented or decremented.

  • The Semaphore.acquire() takes a permit from it and decrements the counter value by one.
  • The Semaphone.release() method returns back the permit to the Semaphore and increments the counter value by one.

If permits are exhausted, or the counter value reaches zero, it means all the shared resources are in use by other threads, then the acquire() method blocks the current Thread until a permit is available to the Semaphore.

There are different types of Semaphores present in Java like,

  • Binary Semaphore
  • Counting Semaphore
  • Timed Semaphore
  • Bounded Semaphore

Out of these, Binary Semaphore is used the most in day-to-day coding. A binary semaphore can have a counter value of either 0 or 1. It means that binary Semaphore protects access to a single shared resource and provides mutual exclusion that allows only one thread to access a critical resource at a time.

Let’s now look at an example of how to create a Binary Semaphore and how we can use its methods.

Semaphore binarySemaphore = new Semaphore(1);

// Acquiring semaphore
binarySemaphore.acquire();

// Printing Available Permits
System.out.println(binarySemaphore.availablePermits())      // 0

// Releasing semaphore
binarySemaphore.release();

// Printing Available Permits
System.out.println(binarySemaphore.availablePermits())      // 1

Let us understand with pseudo-code how to use a Semaphore.

class X {
   private final Semaphore semaphore = new Semaphore(1);
   // ...

   public void m() {
     try {
        semaphore.acquire();
       // ... method body
     } finally {
       semaphore.release();
     }
   }
 }

2. What is a ReentrantLock?

The ReentrantLock implements the Lock interface and works almost similar to the synchronized keyword but with extended capabilities.

Reentrant means, a Thread can acquire the same lock multiple times without any issue.

  • Internally, Reentrant Lock increments the Thread counter whenever a Thread calls lock() method.
  • It decrements the same counter value whenever Thread calls unlock() method.

A lock will be released by a Thread only when the counter reaches 0 i.e. when a thread reenters the lock, it has to request for the unlock the same number of times to release the lock.

ReentrantLock reentrantLock = new ReentrantLock();

reentrantLock.lock();  // counter=1
reentrantLock.lock();  //counter=2

// Check if the object is locked
System.out.println(reentrantLock.isLocked());      // true

// Check if the lock acquired by current Thread
System.out.println(reentrantLock.isHeldByCurrentThread());      // true

// Release the acquired lock using unlock()
reentrantLock.unlock();  //counter=1
System.out.println(reentrantLock.isLocked());      // true
System.out.println(reentrantLock.getHoldCount());      // 1

// Release the acquired lock again
reentrantLock.unlock();  //counter=0
System.out.println(reentrantLock.isLocked());      // false
System.out.println(reentrantLock.getHoldCount());      // 0

Let us see the pseudo-code for how to use it.

class X {
   private final ReentrantLock lock = new ReentrantLock();
   // ...

   public void m() {
     lock.lock();  // block until condition holds
     try {
       // ... method body
     } finally {
       lock.unlock();
     }
   }
 }

3. Difference between Semaphore and ReentrantLock

Let’s now look at the main differences between the two classes.

3.1. Reentrant Nature

Semaphores are non-reentrant in nature means, we cannot acquire a Semaphore second time in the same Thread. Attempting it will lead to a deadlock (a Thread deadlocked with itself).

On the other hand, Reentrant Lock is reentrant in nature and allows a Thread to lock a particular resource multiple times using lock() method.

3.2 Synchronization Mechanism

Semaphores are a good fit for signal passing (signaling mechanism) where Threads use acquire() & release() methods to mark the start and end of accessing a critical resource.

Reentrant Lock uses locking mechanism to lock a particular resource using lock() method and after doing a certain operation on that resource will release the lock using unlock() method.

3.3 Deadlock Recovery

Semaphores provide a strong deadlock recovery mechanism as it uses a non-ownership release mechanism and hence any Thread can release a permit to recover a stuck or a waiting Thread from deadlock situation.

Deadlock Recovery is a bit difficult in the case of Reentrant Lock as it uses ownership of a Thread to a resource by physically locking it and only the owner Thread can unlock that resource. In case the owner Thread enters an infinite waiting or sleeping state, it is impossible to release the lock of that particular resource, which ends up in a deadlock situation.

3.4 Throwing IllegalMonitorStateException

As in Semaphores, no Thread owns the ownership for acquiring or releasing a permit so any Thread can call release() method to release a permit on any other Thread and no Thread will rise IllegalMonitorStateException.

In Reentrant Locks, a Thread is the owner of a critical shared resource by calling the lock() method on the resource and in case some other Thread calls the unlock() method on that resource without owning the lock of it then it will rise IllegalMonitorStateException.

3.5 Modification

Any Thread can use acquire() & release() methods of Semaphore to modify the available permits in it.

Only the current owner thread that owns a resource by lock() method can modify a ReentrantLock, and no other threads are allowed to do so.

5. When to Use?

Semaphores can be used for non-ownership-release semantics where more than one Thread can enter a critical section, and there will be no need for a locking mechanism to lock a shared resource. The Semaphore, by design, is blind to which thread calls acquire() and release() methods, all it cares about is permit becomes available.

If we need reentrant mutual exclusion or a simple mutex then ReentrantLock is the best choice. Reentrant Lock provides better control over the locking mechanism and allows only one Thread to access the critical section at a time thus providing synchronization and eliminating data inconsistency problems when working in a multithreaded application.

6. Conclusion

We have learned about Semaphores and ReentrantLock with examples. We have also seen some of the main differences between the two and how to choose between them based on our needs and requirements.

Happy Learning !!

Comments

Subscribe
Notify of
guest
0 Comments
Inline Feedbacks
View all comments

About Us

HowToDoInJava provides tutorials and how-to guides on Java and related technologies.

It also shares the best practices, algorithms & solutions and frequently asked interview questions.