Java Counting and Binary Semaphore Tutorial with Example

By Arvind Rai, February 25, 2016
This page will walk through java counting and binary Semaphore tutorial with example. Semaphore has been introduced in JDK 5 within the java.util.concurrent package. Semaphore is a concurrent API which works on the basis of a set of permits. It restricts the use of resources by the threads. A thread before using a resource, acquires a permit from Semaphore or goes on hold until thread does not get permit. Semaphore uses fairness settings to handle the queue of threads which needs permits. Once a permit is issued, the number of available Semaphore permit is decreased and when the permit is retuned back by the thread, its number is increased. If number of permits is more than one, we call it Counting Semaphore and if number of permit is only one, it is called Binary Semaphore. We will discuss counting and binary Semaphore in detail here.

What is Semaphore?

Semaphore plays with a set of permits. Permit is taken and returned back by using two methods of Semaphore class.
acquire(): When a thread needs to access a resource, it acquires the permit from the Semaphore using acquire() method. If the permit is not available, it holds until one is available.
release(): Once the thread is finished using resource, it needs to return the permit. Using release() method of Semaphore, thread releases the permit back to Semaphore.

To work with permits, Semaphore maintains a count of permits. So Semaphore is called Counting Semaphore. When we instantiate Semaphore we need to pass maximum number of permits and fairness settings.
Semaphore(int permits, boolean fair)
The role of fairness setting is to decide the ordering of threads that are in queue to acquire permit by using acquire() method of Semaphore. When the fairness is true, the permit is acquired in First-in First-out (FIFO) policy. It means the thread that request to acquire the permit first, will be given permit first when available. The other threads will get permits in subsequent order. If the fairness settings is false, Semaphore does not give guarantee to acquire permit in FIFO order. Passing fairness setting as an argument is optional, default is false.
In case when maximum number of permit are acquired and if a thread reaches for permit, that thread will be blocked and when any other thread releases the permit, the blocked thread will acquire the permit and becomes unblocked. The use of Semaphores is to restrict the threads to use limited resources. Semaphore guarantees that no two threads can access same resource at a time. If threads are more than the number of resources, they will be queued.
Semaphore makes a pool of resources that are used by threads on the basis of given permission. To use the resource, permission is granted to thread and once threads finishes its task, it returns back the permit and resource is back on pool. The available number of resources is the maximum number of permit which is passed to Semaphore while instantiating. Semaphore encapsulate the synchronization differently from synchronization lock when acquire() is called.
When the maximum number of Semaphore permit is one then it is called Binary Semaphore. In this case, Semaphore has only two states 1 or 0. Binary Semaphore is used for mutual exclusion lock. If a thread gets permit then other threads which are in queue will be blocked and gets permit once that thread will release the permit. It is useful when there is only one resource and we need to ensure that only one thread can access it at a time.
As of now we have discussed only about acquire() and release(). Find the use of some more Semaphore methods.

tryAcquire() : This method does not block the calling thread. If Semaphore has any permit, it is given. In case tryAcquire() gets permit, it returns true otherwise false. The important is that tryAcquire() does not honor fairness setting. Even if the fairness setting is true and there are other threads in queue, still code>tryAcquire() gives the permit first if available.

reducePermits(int reduction) : Using this method, semaphore instance can reduce its maximum number of permits by the given number in the argument.

availablePermits(): It returns the available number of permit at any time.

Counting Semaphore Example

When the maximum number of permits of Semaphore is greater than 1, then it is considered as counting Semaphore. Counting Semaphore is used to restrict the use of resources. Counting Semaphore plays just on the basis of number which is subtracted and added. When the permit is required, number is subtracted by one and when released, it is added by one.
For the Counting Semaphore demo, we are creating a library of books. One book can be issued to only one reader at a time. The other readers can get books only when the more books are available or the books are retuned back to library by the readers who has already issued. The number of readers is more than the number of books in our scenario. What all we want is issue the books to readers if available otherwise hold on till the books are retuned back to the library.
Library.java
package com.concretepage.semaphore.counting;
import java.util.concurrent.Semaphore;
public class Library {
	   private static final int MAX_PERMIT = 3;
	   private final Semaphore availableBook = new Semaphore(MAX_PERMIT, true);
	   private Book[] books = {new Book("Ramayan"), new Book("Mahabharat"), new Book("Panchtantra")};
	   private boolean[] beingRead = new boolean[MAX_PERMIT];
	   
	   //Book is being issued to reader
	   public Object issueBook() throws InterruptedException {
	     availableBook.acquire();
	     return getNextAvailableBook();
	   }
	   private synchronized Book getNextAvailableBook() {
	     Book book = null;  
	     for (int i = 0; i < MAX_PERMIT; ++i) {
	       if (!beingRead[i]) {
	          beingRead[i] = true;
	          book = books[i];
	          System.out.println(book.getBookName()+" has been issued.");
	          break;
	       }
	     }
	     return book;
	   }

	   //Book is being returned to library
	   public void returnBook(Book book) {
		     if (markAsAvailableBook(book))
		       availableBook.release();
	   }	   
	   private synchronized boolean markAsAvailableBook(Book book) {
	     boolean flag = false;  
	     for (int i = 0; i < MAX_PERMIT; ++i) {
	       if (book == books[i]) {
	          if (beingRead[i]) {
	            beingRead[i] = false;
	            flag = true;
		        System.out.println(book.getBookName()+" has been returned.");
	          } 
	          break;
	       }
	     }
	     return flag;
	   }
} 
Reader.java
package com.concretepage.semaphore.counting;
public class Reader implements Runnable {
	private Library library;
	public Reader(Library library) {
		this.library = library;
	}
	@Override
	public void run() {
		try {
			Book book = (Book)library.issueBook();
			book.read();
			library.returnBook(book);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}
} 
Book.java
package com.concretepage.semaphore.counting;
public class Book {
	private String bookName;
	public Book(String bookName) {
		this.bookName = bookName;
	}
	public void read() {
		System.out.println(bookName + " is being read......");
		try {
			Thread.sleep(2000);
		}catch(InterruptedException e) {
			e.printStackTrace();
		}
	}
	public String getBookName() {
		return bookName;
	}
} 
Main.java
package com.concretepage.semaphore.counting;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class Main {
	public static void main(String[] args) {
		final int threadCount = 6;
		final ExecutorService exService = Executors.newFixedThreadPool(threadCount);
		final Library library = new Library();
		for(int i=0; i < threadCount; i++) {
			Reader reader = new Reader(library);
			exService.execute(reader);
		}
		exService.shutdown();
	}
} 
Observe the output. If a book is issued then that book has been retuned back before issuing again to any reader.
Ramayan has been issued.
Mahabharat has been issued.
Panchtantra has been issued.
Mahabharat is being read......
Ramayan is being read......
Panchtantra is being read......
Mahabharat has been returned.
Mahabharat has been issued.
Mahabharat is being read......
Ramayan has been returned.
Ramayan has been issued.
Ramayan is being read......
Panchtantra has been returned.
Panchtantra has been issued.
Panchtantra is being read......
Ramayan has been returned.
Mahabharat has been returned.
Panchtantra has been returned. 

Binary Semaphore Example

Binary Semaphore has only one permit. It acts like binary number 1 or 0. If a thread acquires permit, then all the other threads will be on hold to get permit till the permit is released. Binary Semaphore is used as mutual exclusion lock. It avoids deadlock.
For the Binary Semaphore demo, we have taken the example of printer. We will fire many jobs to printer but printer will perform only one task at a time and other job will be in queue.
Printer.java
package com.concretepage.semaphore.binary;
import java.util.concurrent.Semaphore;
public class Printer {
	   private static final int MAX_PERMIT = 1;
	   private final Semaphore semaphore = new Semaphore(MAX_PERMIT, true);
	   public void print(String jobName) {
	          try {
			semaphore.acquire();
			System.out.println("Printing Job: "+ jobName);				
		        Thread.sleep(2000);
			System.out.println("Finished Job: "+ jobName);	
		  } catch (InterruptedException e) {
			e.printStackTrace();
		  }finally {
		        semaphore.release();
		  }
	   }
} 
Job.java
package com.concretepage.semaphore.binary;
public class Job implements Runnable {
	private Printer printer;
	private String jobName;  
	public Job(Printer printer, String jobName) {
		this.printer = printer;
		this.jobName = jobName;
	}
	@Override
	public void run() {
		System.out.println("Job sent to printer:"+ jobName);		
		printer.print(jobName);
	}
} 
Main.java
package com.concretepage.semaphore.binary;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class Main {
	public static void main(String[] args) {
		final int threadCount = 5;
		final ExecutorService exService = Executors.newFixedThreadPool(threadCount);
		final Printer printer = new Printer();
		for (int i=1; i<= threadCount; i++) {
			exService.execute(new Job(printer, "Job-"+i));
		}
		exService.shutdown();
	}
} 
Observe the output. Printer is performing only one job at a time.
Job sent to printer:Job-1
Printing Job: Job-1
Job sent to printer:Job-5
Job sent to printer:Job-2
Job sent to printer:Job-4
Job sent to printer:Job-3
Finished Job: Job-1
Printing Job: Job-5
Finished Job: Job-5
Printing Job: Job-2
Finished Job: Job-2
Printing Job: Job-4
Finished Job: Job-4
Printing Job: Job-3
Finished Job: Job-3 


I am done now. Happy java learning!

Download Source Code

POSTED BY
ARVIND RAI
ARVIND RAI







©2024 concretepage.com | Privacy Policy | Contact Us