What is a Java semaphore?

Java semaphores are one of the most important constructs for working with multiple threads or processes in the Java programming language. But what are Java semaphores exactly, and how do Java semaphores work?

What are Java semaphores?

In concurrent and parallel programming, semaphores are constructs that control the number of processes or threads that can simultaneously access a given resource.

The implementation of semaphores under the hood is fairly simple: each semaphore has a counter that contains an integer value. If a thread wants to access the resource and the counter is currently greater than zero, the thread acquires a “permit” and the counter is decremented by 1. If the counter is not greater than zero, then the semaphore blocks the thread until a permit is available.

When the thread has finished its work, it releases the permit back to the semaphore, and the counter is incremented by 1. If another thread is waiting, this second thread can now acquire the permit that has just been released.

One well-known type of semaphore is the mutual exclusion lock, a synchronization mechanism that allows only a single thread to access a resource at any time. Initializing the semaphore’s counter to 1 will essentially create a lock that allows only 1 thread to access the given resource.

There are several common use cases for semaphores: for example, limiting the number of users who can log into a system at the same time, or preventing a large number of threads from consuming too many system resources. Java allows developers to use semaphores through the java.util.concurrent.Semaphore class. 

How do Java semaphores work?

There are several important things to know about how Java semaphores work. Below are some of the most important methods of Java semaphores:

  • acquire():This method acquires a permit from the given semaphore, blocking until a permit is available or until the thread is interrupted. Optionally, you can provide an integer parameter specifying the number of permits to acquire.
  • availablePermits(): Returns the number of permits that are available from the given semaphore.
  • drainPermits(): Acquires and returns all permits that are immediately available.
  • hasQueuedThreads(): Returns true if the semaphore currently has any threads that are waiting to acquire a permit.
  • release(): This method releases a permit and returns it to the semaphore.
  • reducePermits(): This method reduces the available permits in the semaphore by the given number.
  • tryAcquire(): This method tries to acquire a permit from the given semaphore if one is currently available.

Java semaphores in Redis

Redis is an open-source, in-memory data structure store used to implement NoSQL key-value databases, caches, and message brokers. The good news for Redis users is that you can build a semaphore in Redis using the ZSET (sorted set) data structure. The bad news, however, is that this can be a difficult and time-consuming task, especially for developers who are new to working in Redis.

For this reason, many Redis users install third-party Redis Java clients such as Redisson. Redisson includes a wide variety of familiar Java objects, collections, and constructs to ease the learning curve when using Redis.

In particular, Redisson includes support for Java semaphores in Redis via the RSemaphore interface. Below is an example of how to use RSemaphore:

RSemaphore semaphore = redisson.getSemaphore("mySemaphore");

// acquire single permit
semaphore.acquire();

// or acquire 10 permits
semaphore.acquire(10);

// or try to acquire permit
boolean res = semaphore.tryAcquire();

// or try to acquire permit or wait up to 15 seconds
boolean res = semaphore.tryAcquire(15, TimeUnit.SECONDS);

// or try to acquire 10 permit
boolean res = semaphore.tryAcquire(10);

// or try to acquire 10 permits or wait up to 15 seconds
boolean res = semaphore.tryAcquire(10, 15, TimeUnit.SECONDS);
if (res) {
   try {
     ...
   } finally {
       semaphore.release();
   }
}

RSemaphore also comes with Async, Reactive, and RxJava2 interfaces, so that you can use the programming model that best fits your purposes.

Redisson also comes with the RPermitExpirableSemaphore interface, a variant of RSemaphore that allows threads to acquire permits with an optional time limit. The code below demonstrates the usage of RPermitExpirableSemaphore with and without the time limit:

RPermitExpirableSemaphore semaphore = redisson.getPermitExpirableSemaphore("mySemaphore");
String permitId = semaphore.acquire();

// acquire permit with a lease time of 2 seconds
String permitId = semaphore.acquire(2, TimeUnit.SECONDS);
// ...
semaphore.release(permitId);
Similar terms