java.util.concurrent: Java 5 Semaphore

This post is a continuation in the series on Java 5 concurrency utilities. My previous posts dealt with Atomic classes and Locks. This one will focus on the Semaphore class.

The sole purpose of the Semaphore class is to limit the amount of concurrent access. Consider one has a resource intensive component; a business component which requires lot of memory for computational processing. We cannot have a large number of consumers concurrently accessing this component. Given the finite size of memory, uncontrolled concurrent access would lead to memory contention and inevitable slowdown. To overcome this challenge Java 5 provides the Semaphore class. To use the Semaphore, client program needs to instantiate the Semaphore instance and specify the maximum number of threads which can concurrently access the resources guarded by the Semaphore instance. The value limit is termed as “permits”.

For a consumer thread to procure a permit from a semaphore, the consumer thread is provided with two options. InvokeĀ  acquire or tryAcquire method on the Semaphore instance. The acquire method is a blocking thread which will block until a permit is made available to it. On the other hand tryAcquire will attempt only once to acquire a permit; if unsuccessful it will gracefully exit without blocking. Both the methods have overloaded versions which allow them to specify the number of permits they want to acquire. The tryAcquire method has overloaded versions which allow the consumer to wait for a permit(s) acquisition for a pre-defined period of time. To release a permit or permits back to the Semaphore instance, the consumer needs to invoke release or its overloaded cousins. The key point to note here is that the Semaphore instance does not maintain any record of which threads acquired permits and which ones are releasing permits. It is the responsibility of the application developer to maintain conformity if desired. ThisĀ  facility is a plus or a minus from an end user’s viewpoint. I would have preferred if the Semaphore class maintained such history. But that was not to be.

Anyways enough theory, let’s look at how we can go about implementing a Semaphore. Consider the following class ComplexBusinessProcess.

package com.test.concurrency.semaphore;

import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.concurrent.Semaphore;

public class ComplexBusinessProcess {

	private static final int MAX_THREADS = 3;

	private static Semaphore accessControl = new Semaphore(MAX_THREADS);

	private String name = null;

	public ComplexBusinessProcess(String name) {
		this.name = name;
	}

	public void doSomething() {
		//Does some processing which is extremely complex and
		// memory intensive. Need to limit access to a specific no.
		// of threads

		//Check if permit is available
		if (this.acquireAccess()) {
			printMessage("Complex processing started for " + this.name + ".");
		}

		try {
			Thread.sleep(5000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}

		this.releaseAccess();
		printMessage("Processing completed by " + this.name + ".");

	}

	public void doSomethingConditionally() {
		//Does some processing which is extremely complex and
		// memory intensive. Need to limit access to a specific no.
		// of threads

		//Check if permit is available
		if (this.attemptAccess()) {
			printMessage("Complex processing started for " + this.name + ".");
		} else {
			printMessage("Complex processing not initiated for " + this.name + ".");
			return;
		}

		try {
			Thread.sleep(5000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}

		this.releaseAccess();
		printMessage("Processing completed by " + this.name + ".");

	}

	private static void printMessage(String message) {
		DateFormat df = new SimpleDateFormat("HH:mm:ss");
		System.out.println(df.format(new java.util.Date()) + " " + message);
	}

	private boolean acquireAccess() {
		try {
			accessControl.acquire();
		} catch (InterruptedException e) {
			e.printStackTrace();
			throw new RuntimeException("Unable to acquire Semaphore.", e);
		}
		return true;
	}

	private boolean attemptAccess() {
		return accessControl.tryAcquire();
	}

	public boolean releaseAccess() {
		accessControl.release();
		return true;
	}

}

The ComplexBusinessProcess class models a resource intensive business process. It instantiates a Semaphore instance variable named accessControl. The instance is constructed with limit of three permits. The acquisition and release of permit is facilitated via three methods namely acquireAccess, attemptAccess and releaseAccess. Two business methods doSomething and doSomethingConditional are used to replicate the behaviour of complex processing. Currently these methods simply cause the prevailing thread to sleep for 5 seconds. The doSomething method tries the blocking acquisition and doSomethingConditional method tries the nonblocking acquisition route.

The functionality can be tested using the following test classes. SemaphoreTest and Processor is used for testing the doSomething method implementation and ConditionalSemaphoreTest and ConditionalProcessor for testing the doSomethingConditional method implementation. Please find below the source code.

package com.test.concurrency.semaphore;

public class Processor implements Runnable {

	private String threadName = null;

	public Processor(String name) {
		this.threadName = name;
	}

	public void run() {
		ComplexBusinessProcess busProcess = new ComplexBusinessProcess(this.threadName);
		busProcess.doSomething();
	}

}

package com.test.concurrency.semaphore;

public class SemaphoreTest {

	public static void main(String[] args) {
		for(int i=1; i<5; i++) {
			Processor processor = new Processor("THREAD-" + i);
			new Thread(processor).start();
		}
	}

}

package com.test.concurrency.semaphore;

public class ConditionalProcessor implements Runnable {

	private String threadName = null;

	public ConditionalProcessor(String name) {
		this.threadName = name;
	}

	public void run() {
		ComplexBusinessProcess busProcess = new ComplexBusinessProcess(this.threadName);
		busProcess.doSomethingConditionally();
	}

}

package com.test.concurrency.semaphore;

public class ConditionalSemaphoreTest {

	public static void main(String[] args) {
		for(int i=1; i<5; i++) {
			ConditionalProcessor processor = new ConditionalProcessor("THREAD-" + i);
			new Thread(processor).start();
		}
	}

}

The test client code is self-explanatory.

Console output for SemaphoreTest:

12:14:21 Complex processing started for THREAD-4.
12:14:21 Complex processing started for THREAD-1.
12:14:21 Complex processing started for THREAD-2.
12:14:26 Processing completed by THREAD-2.
12:14:26 Complex processing started for THREAD-3.
12:14:26 Processing completed by THREAD-1.
12:14:26 Processing completed by THREAD-4.
12:14:31 Processing completed by THREAD-3.

The test client code tries to run 4 threads. 3 threads pass thru successfully and the fourth is blocked due to non-availiability of permits. On completion of one of the initial threads in our case Thread-2, the fourth thread is allowed to process.

Console ouput for ConditionalSemaphoreTest:

12:16:45 Complex processing started for THREAD-4.
12:16:45 Complex processing started for THREAD-2.
12:16:45 Complex processing not initiated for THREAD-3.
12:16:45 Complex processing started for THREAD-1.
12:16:50 Processing completed by THREAD-1.
12:16:50 Processing completed by THREAD-2.
12:16:50 Processing completed by THREAD-4.

Unlike the blocking implementation of acquire, the tryAcquire tries to acquire a permit and on non-availiability gracefully terminates the operation and informs the consuming application of the non-availiability of the permit.

Semaphore is a nice utility which would assist application developers in restricting/throttling access to resource intensive components. My only point of contention is the fact that the acquiring thread and the releasing thread have not coherence. Although I agree this would be a useful functionality to prevent or to get out of a deadlock situtation, I would have preferred if the application developer has the flexibility to decide whether he/she wants to enforce such stringent checks or relax this validation.

Advertisements

4 thoughts on “java.util.concurrent: Java 5 Semaphore

  1. I’m trying to do a project for school, and I’m really having a hard time understanding semaphores. I am trying to outline what my project is going to do before I start coding, and I’m really having problems with that too. I’d really like some help or guidance, if you have the time. =)

    Project:
    Write a java program that simulates the production of sugar from two different ingredients, i1 and i2. The simulation must generate sugar from 5 units of i1 and 3 units of i2, each provided by many processes of type A and B. Each process starts up, provides an ingredient for making sugar before terminating. If no sugar can be made, the process must wait before it can terminate. Your solution must avoid starvation and busy waiting meaning that 1) sugar is generated whenever the correct ratio of ingredients becomes available and 2) no process is waiting in a busy loop. Process A calls i1Available() to indicate the availability of one unit of i1. Process B calls i2Available() to indicate the availability of one unit of i2. Either method can call MakeSugar() to create sugar. Set up 50 threads of type A and 30 threads of type B. Use semaphores to implement your solution.

    I’m trying to do something like:
    P1 – attempt access
    if (requirements to make sugar)
    makesugar()
    else if (i1<5)
    i1++
    else if (i1==5)
    wait for i2==3

    and the same for P2.

    Please let me know if you have any insight or suggestions for me. I'd really appreciate it.

    Casey

    1. Casey,

      Semaphores are of two types binary and counting. The binary semaphore is like a synchronized block in Java; you either have access or you do not. The counting semaphore on the other hand controls access to a resource intensive process like memory. The example I have modeled in my post covers counting semaphore.

      In your example, process A and B can be mapped as expensive processes which have a defined upper limit for the number of threads that can concurrently access them. Whether you want the threads to wait or exit gracefully is your call. Note the count of available ingredients do not model a semaphore. Semaphore protects expensive resources from user access beyond acceptable limits.

      I hope my explanation is clear.

  2. I understand that mutual exclution means that only one process can access a shared resource at a time and to achieve that one has to use entry and exit protocols. I want to know what will happen if one or more processes acess a semaphore concurrently.

  3. Nametso Lewaneka,

    Semaphore does not need to be limited to mutex scenario only. It can be used to limit access to certain limit. For example, you want to throttle the HTTP requests to a server. This can be achieved by defining a semaphore with a limit say 100. Thus the server can cater to a upper limit of only 100 concurrent users. As far as the process is concerned it should be fine with concurrent access. The point of semaphore is to limit concurrent access to protect resource usage like memory, CPU etc. It is not meant to be a tool for shared data protection unless we define a semaphore with value 1.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s