Java Binary Semaphore (+Example)

A binary semaphore is a synchronization primitive that can hold only two values, typically 0 and 1. We use binary semaphore to manage access to a shared resource by multiple threads in a concurrent environment. Conceptually, a binary semaphore can be thought of as a lock that allows …

A binary semaphore is a synchronization primitive that can hold only two values, typically 0 and 1. We use binary semaphore to manage access to a shared resource by multiple threads in a concurrent environment.

Conceptually, a binary semaphore can be thought of as a lock that allows only one thread to access a critical section of code at a time.

1. How does a Binary Semaphore work?

A binary semaphore can be visualized as a flag that indicates whether a resource is available or not. It has only two states: 0 (unavailable) and 1 (available).

  • Initially, a binary semaphore is initialized with a value of 1, indicating that the resource is available.
  • When a thread acquires the semaphore, the value is decremented from 1 to 0, making the resource unavailable.
  • When the thread releases the semaphore, the value is incremented from 0 to 1, making the resource available again.

This is essential to understand that the semaphore value must always be within the limits of 0 and 1.

When a thread has finished the use of the shared resource, it must release the semaphore so that the other threads can access the shared resource. That operation increases the internal counter of the semaphore.

2. When to use Binary Semaphore?

As discussed above, a binary semaphore can have a value of either 0 or 1. It means binary semaphore protect the access to a SINGLE shared resource, so the internal counter of the semaphore can only take the values 1 or 0.

So whenever you have a requirement for protecting the access to a SINGLE resource accessed by multiple threads, you can use Binary Semaphore.

Similarly, we can use a binary semaphore to ensure that only one thread accesses a critical section at a time.

Read More: How to Use Locks in Java

3. How to use a Binary Semaphore?

To show the usage, we are going to implement a print queue using binary semaphore that can be used by concurrent tasks to print their jobs. This print queue will be protected by a binary semaphore, so only one thread can print at a time.

3.1. PrintingJob

This class represents an independent printing that could be submitted to a printer. This class implements Runnable interface so that the printer can execute it when its turn comes.

public class PrintingJob implements Runnable {

   private PrinterQueue printerQueue;
 
   public PrintingJob(PrinterQueue printerQueue) {
      this.printerQueue = printerQueue;
   }
 
   @Override
   public void run() {
      System.out.printf("%s: Going to print a document\n", Thread.currentThread().getName());
      printerQueue.printJob(new Object());
   }
}

3.3. PrinterQueue

This class represents the printer queue/ printer. Please note that we pass the value 1 as the parameter of this Semaphore’s constructor, so you are creating a binary semaphore.

public class PrinterQueue {

   private final Semaphore semaphore;
 
   public PrinterQueue(){
      semaphore = new Semaphore(1);
   }
 
   public void printJob(Object document) {
      try {
         semaphore.acquire();
          
         Long duration = (long) (Math.random() * 10000);
         System.out.println(Thread.currentThread().getName() + ": PrintQueue: Printing a Job during " + (duration / 1000) + " seconds :: Time - " + new Date());
         Thread.sleep(duration);
      } 
      catch (InterruptedException e) {
         e.printStackTrace();
      } finally {
         System.out.printf("%s: The document has been printed\n", Thread.currentThread().getName());
         semaphore.release();
      }
   }
}

3.3. Demo

Let’s test our printer program:

public class SemaphoreExample
{
   public static void main(String[] args)
   {
      PrinterQueue printerQueue = new PrinterQueue();
      Thread thread[] = new Thread[10];
      for (int i = 0; i < 10; i++)
      {
         thread[i] = new Thread(new PrintingJob(printerQueue), "Thread " + i);
      }
      for (int i = 0; i < 10; i++)
      {
         thread[i].start();
      }
   }
}

The program output:

Thread 0: Going to print a document
Thread 9: Going to print a document
Thread 8: Going to print a document
Thread 5: Going to print a document
Thread 7: Going to print a document
Thread 6: Going to print a document
Thread 3: Going to print a document
Thread 4: Going to print a document
Thread 2: Going to print a document
Thread 1: Going to print a document
Thread 0: PrintQueue: Printing a Job during 3 seconds :: Time - Tue Jan 06 18:00:12 IST 2015
Thread 0: The document has been printed
Thread 9: PrintQueue: Printing a Job during 0 seconds :: Time - Tue Jan 06 18:00:16 IST 2015
Thread 9: The document has been printed
Thread 8: PrintQueue: Printing a Job during 7 seconds :: Time - Tue Jan 06 18:00:16 IST 2015
Thread 8: The document has been printed
Thread 5: PrintQueue: Printing a Job during 0 seconds :: Time - Tue Jan 06 18:00:24 IST 2015
Thread 5: The document has been printed
Thread 7: PrintQueue: Printing a Job during 4 seconds :: Time - Tue Jan 06 18:00:24 IST 2015
Thread 7: The document has been printed
Thread 6: PrintQueue: Printing a Job during 3 seconds :: Time - Tue Jan 06 18:00:29 IST 2015
Thread 6: The document has been printed
Thread 3: PrintQueue: Printing a Job during 8 seconds :: Time - Tue Jan 06 18:00:33 IST 2015
Thread 3: The document has been printed
Thread 4: PrintQueue: Printing a Job during 0 seconds :: Time - Tue Jan 06 18:00:41 IST 2015
Thread 4: The document has been printed
Thread 2: PrintQueue: Printing a Job during 4 seconds :: Time - Tue Jan 06 18:00:42 IST 2015
Thread 2: The document has been printed
Thread 1: PrintQueue: Printing a Job during 3 seconds :: Time - Tue Jan 06 18:00:46 IST 2015
Thread 1: The document has been printed

Look at the printJob() method. This method shows the three steps you must follow when you use a semaphore to implement a critical section and protect access to a shared resource:

  • First, you acquire the semaphore, with the acquire() method.
  • Then, you do the necessary operations with the shared resource.
  • Finally, release the semaphore with the release() method.

4. Semaphore Fairness

The java.util.concurrent.Semaphore class accepts a second parameter in its constructor. This parameter must take a Boolean value.

  • If you give it the false value, you are creating a semaphore that will work in non-fair mode. This is the default behavior.
  • If you give it the true value, you are creating a semaphore that will work in fair mode.
Semaphore semaphore = new Semaphore(1);   // Unfair Semaphore (Default)

Semaphore fairSemaphore = new Semaphore(1, true);  // Fair Semaphore

The fairness mode decides how threads acquire permits when they are available.

In unfair mode, there is no guarantee of the order in which threads will acquire permits. The semaphore simply grants a permit to any waiting thread that happens to wake up first. Due to this, certain threads may acquire permits more frequently, causing other threads to starve if they are continually preempted by others.

In fair mode, the semaphore ensures that threads acquire permits in the order they requested them (FIFO – First In, First Out). This is implemented by maintaining a queue of waiting threads. This ensures that no thread is starved.

Happy Learning !!

Weekly Newsletter

Stay Up-to-Date with Our Weekly Updates. Right into Your Inbox.

Comments

Subscribe
Notify of
4 Comments
Most Voted
Newest Oldest
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.

Morty Proxy This is a proxified and sanitized view of the page, visit original site.