Semaphores in JDK 1.5

This article was originally published in VSJ, which is now part of Developer Fusion.
You can perform multiple tasks at the same time using the thread support in the Java programming language. Originally conceived for writing server programs that run on hardware with multiple processors, multi-threaded programming is now a standard item in every Java programmer’s toolbox. It is important to understand what threads are, how to use them, and when they should be used.

This article introduces you to the fundamentals of concurrent processing using Java threads. It includes several functional yet easy to follow hands-on examples, designed to illustrate the basic concepts and to provide a framework for further experimentation. Instead of adopting the classic approach using JDK 1.1-style synchronization primitives, this article will explore the simplicity of JDK 1.5’s new semaphore.

About Java threads

Most likely, you write, compile, and test Java code on your desktop PC. Most programs run from the beginning to end in just one single execution flow. These programs can be written without explicitly working with threads.

If you are using any of the GUI libraries (for example Swing), or if you are writing server-based program using JSP or servlets, you have already worked with threads. In essence, threads enable your program to have multiple simultaneous execution flows active. Figure 1 shows how server programs and GUI programs take advantage of threads.

Figure 1: Typical uses of threads
Figure 1: Typical uses of threads

Server programs have tough performance requirements, and need to process multiple requests coming from different clients at the same time to be useful. GUI applications need to update the display at the same time as handling user input in order to “feel” responsive to the user. These are common situations where there is a definite need to have a Java program to do more than one thing at a time. Each flow of execution in these cases is executed on a thread.

Simultaneous execution on a single CPU

On systems that have multiple hardware CPUs, mostly server systems, each CPU can run a different thread. If you have multiple tasks, independent of one another, they will complete execution in a shorter period of time since they execute simultaneously (concurrently, in Java lingo). This can be used to great advantage on server systems since incoming client requests are essentially independent of one another. Server applications are intrinsically multi-threaded.

Thread usage is not restricted to server systems with multiple CPUs, however. Threads are part of the Java platform, and are available to you even on systems with a single CPU – like your desktop PC. In the situation of a single CPU, threads are actually not executed concurrently. Instead, the operating system manages the multiple flow of execution by repeatedly stopping one and starting another. In other words, simultaneous execution is actually an illusion on a single CPU system.

You may be wondering by now why it would be beneficial to simulate simultaneous execution on a single CPU machine. Would it not introduce overheads in switching between the different tasks, and actually take more time to complete all the tasks?

Figure 2: Task switching
Figure 2: Task switching

Yes, it would. Figure 2 illustrates this phenomenon: you can see that by slicing two tasks and simulating simultaneous execution, the total time taken to complete both tasks has actually increased. But note the completion time of the two tasks. Each task takes more time to complete than when it has the CPU all by itself. However, each completes in less time than if it were to be second in line in an orderly execution. This is one of the key reasons why it is desirable to use multiple threads even on single CPU machines.

The other key advantage has to do with input and output (or I/O for short). While the CPU runs at a high speed on most modern systems, input and output is still very slow. For example, when your application needs to write data to the disk, it may take a significant time to complete writing. If the entire CPU is halted waiting for this completion and doing nothing else, much of its computation power is just wasted. Using multiple threads enables another flow of execution, not involving the same I/O, to proceed without waiting. This approach maximizes the use of the CPU. Without exception, all modern operating systems are multi-threaded. The Java programming language simply passes on this capability to the application programmer.

Consider the case of the Graphical User Interface (GUI). It has to handle user input at the same time as updating the graphical display. By using multiple threads, the GUI can stay responsive to the user at any time. Waiting for user input can be a very slow task, when compared to the speed of the CPU, consuming many CPU cycles. By having the CPU switching to another thread, useful work such as updating the display, can be performed while the user is slowly entering the input. Thanks to the use of multiple threads, the GUI system can stay responsive. All modern GUIs use multiple threads during execution.

In your own application design and programming, you may be able to identify many situations where it would be advantageous to use multiple threads. It is important to know how to use the thread creation and management API.

Creating Java threads

Figure 3 shows the inheritance hierarchy of the Thread class in Java.

Figure 3: Thread class hierarchy
Figure 3: Thread class hierarchy

Every instance of Thread implements the Runnable interface. The Runnable interface specifies the work that the thread will perform. The Runnable interface has only one single method, shown below:

public interface Runnable {
	public void run();
}
Since a Thread instance must implement the Runnable interface, you can subclass Thread and override the run() method to perform your own work. Most likely, however, the work is isolated in a non-Thread based object that implements the Runnable interface. With such an object, you can use the following constructor for Thread:
Thread(Runnable target);
This will make the thread do the work in the target’s run() method. The “work” to be performed by the thread is placed in the run() method of the target (an object that implements the Runnable interface).

When threads are first created, they are in the runnable state, but are not started by default. The start() method of the thread must be called explicitly to start a thread running.

Time to see some code that actually creates multiple threads.

Running with two threads

The first example creates two threads. Each thread prints different characters to the screen.

The code for the example is in the TestThread1.java class of the code distribution. It is reproduced below with annotations:

public class TestThread1 {
	private static TestThread1 testthrd;
	private static Thread t1;
	private static Thread t2;
	private static final int DELAY
		= 100;
The two threads are stored in static variables t1 and t2. The work they do is specified by two private classes below: Work1, and Work2. You can see that the actual work done is inside the run() method of these private classes.

The first class, Work1, prints out the characters “0123456789”, with a delay of 100 milliseconds between each character:

private class Work1 implements
	Runnable {
	public void run() {
		char t = ‘0’;
		while (true) {
			System.out.print(t);
			if (++t > ‘9’) t = ‘0’;
			try {
				Thread.sleep(DELAY);
			} catch (Exception ex) {}
		} // of while
	}
}
The second class, Work2, prints out the characters “abcdefghijklmopqrstuvwxyz”, also with a delay of 100 milliseconds between each character:
private class Work2 implements
	Runnable {
	public void run() {
		char t = ‘a’;
		while (true) {
			System.out.print(t);
			if (++t > ‘z’) t = ‘a’;
			try {
				Thread.sleep(DELAY);
			} catch (Exception ex) {}
		} // of while
	}
The method used to create the threads and start them is the startThreads() method. Note how each of the threads is created using the new operator, and supplying an instance of Work1/Work2 as the work to perform. Each thread is started explicitly by calling its start() method:
public void startThreads() {
	t1 = new Thread(new Work1());
	t2 = new Thread(new Work2());
	t1.start();
	t2.start();
}
In the main() method, an instance of TestThread() is created, and then its startThreads() is called to start the two threads writing to the screen:
public static void main(
	String [] args) {
	testthrd = new TestThread1();
	testthrd.startThreads();
	}
}
The examples assume you have JDK 1.5 installed and ready. Actually, this particular example does not use any of the JDK 1.5 features, but the next example needs them.

Compile and run TestThread1. What you will see on the screen is similar to Figure 4.

Figure 4: Result of running TestThread1
Figure 4: Result of running TestThread1

Synchronizing Threads

In Figure 4, you can see that the output from the two threads is intermixed. The digits from ‘0’ to ‘9’ are hopelessly mixed with the characters ‘a’ to ‘z’. This is to be expected, and confirms that the two threads are indeed executing concurrently. There are many programming situations where you don’t want this random shuffling to happen. For example, you may be updating an array in one thread, and you do not want another thread to be “switched in” while you are updating. If you let another thread run, it may modify the values in the array in between your own modifications and cause data corruption.

In order to be protected against this random shuffling, you need to synchronize the execution of the two threads. In this case, you need to make sure that t1 has finished printing ‘0123456789’ before allowing t2 to print ‘abcdefghijklmnopqrstuvwxyz’ (and vice versa).

A variety of ways to accomplish this synchronization are available. One of the simplest is to use JDK 1.5’s new Semaphore class.

JDK 1.5’s Semaphore class

Semaphore is one of the versatile classes in the java.util.concurrent package. This package is a standard part of the JDK 1.5 libraries.

A semaphore can be used to protect access to a shared resource. In the example, the resource being protected is the output stream to the screen. Each semaphore maintains a number of permits. A thread can ask the semaphore for a permit by calling its acquire() method. If no permits are available, the thread will block. Blocking means that it will wait until a permit is available. Meanwhile, other threads will continue to run. Once a thread obtains a permit, it can start using the shared resource.

For example, t1 may call acquire() on the semaphore to obtain a permit. Once it gets the permit, it will print the characters ‘0123456789’, delaying 100 milliseconds between each character. Meanwhile, t2 will try to call acquire() on the same semaphore, but be blocked. As soon as t1 finishes its work, it will call release() on the semaphore. Calling release() on the semaphore returns the permit to the semaphore, allowing it to give the permit to the blocked t2. When t2 gets the permit, it proceeds to print ‘abcdefghijklmopqrstuvwxyz’. This repeats itself as t1 attempts to acquire() a permit and blocks waiting for t2 to release the permit.

Using Semaphore to synchronize screen output

The synchronized version of the example is called ThreadSync.java. The code of ThreadSync.java is reproduced here, with annotations.
import java.util.concurrent.*;
public class ThreadSync {
	private static ThreadSync thrdsync;
	private static Thread t1;
	private static Thread t2;
	private static final int DELAY = 100;
In the code below, the semaphore is created. In the constructor, you specify the number of permits to manage as an argument. In this case, it is 1. This means that only one thread can gain access to the screen output at a time. The code in Work1 now attempts to call acquire() before printing the ‘0’ character. It also call release() after printing the ‘9’ character.
	private static Semaphore sm = new Semaphore(1);
	private class Work1 implements Runnable {
		public void run() {
			char t = ‘0’;
			while (true) {
				if (t == ‘0’)
					try {
						sm.acquire();
					} catch (Exception ex) {
						break;
					}
					System.out.print(t);
				if (++t > ‘9’) {
					sm.release();
					t = ‘0’;
				}
				try {
					Thread.sleep(DELAY);
				} catch (Exception ex) {}
			} // of while
		}
	}
The code in the run() method of Work2 will now attempt to acquire the semaphore before printing the letter ‘a’, and will release the semaphore after printing the ‘z’ character.
private class Work2 implements
	Runnable {
	public void run() {
		char t = ‘a’;
		while (true) {
			if (t == ‘a’)
				try {
					sm.acquire();
				} catch (Exception ex) {
					break;
				}
			System.out.print(t);
			if (++t > ‘z’) {
				t = ‘a’;
				sm.release();
			}
			try {
				Thread.sleep(DELAY);
			} catch (Exception ex) {}
		} // of while
	}
}
The startThreads() and main() methods remain the same as the original example.
public void startThreads() {
	t1 = new Thread(new Work1());
	t2 = new Thread(new Work2());
	t1.start();
	t2.start();
}
public static void main(
	String [] args) {
	thrdsync = new ThreadSync();
	thrdsync.startThreads();
}
}
Compile and run ThreadSync. You should see output similar to Figure 5: you can see that now the two threads share the screen in harmony: the output from the threads is no longer shuffled. Each thread holds on to the semaphore until it finishes its sequence of printing.

Figure 5: Result of running ThreadSync
Figure 5: Result of running ThreadSync

Orderly shutdown by synchronizing three threads

There is still a minor problem. The only way to shut down ThreadSync is by hitting Ctrl-c or the Break key. This abruptly terminates the program. The work preformed by the threads may be partially completed. In our example, the partially completed work is trivially a partially printed character sequence – not a serious consequence. In real production, abrupt forceful shutdown may stop a thread that is updating data in the middle of its work. This may cause corrupted data – resulting in disastrous consequences. Therefore, it is very important to know how to shutdown an application with multiple threads gracefully

To understand how to shutdown the application gracefully, you need to realize that the application actually has three concurrent threads running – and not two. Where is the third thread?

The third thread is actually the main application thread. This is the thread that creates the other two by executing the main() method and calling the startThreads() method.

The third and last example will use this thread to obtain input from the keyboard. It will look for the entry of a ‘.’ (dot) character. When it receives this character from the user, it will terminate the application gracefully.

Note that all three threads execute concurrently, which means that the main thread will be looking for user input while the output is being printed by the other two threads.

The orderly shutdown is performed by the main application thread, using the following approach:

  1. acquiring the semaphore, this will ensure that both of the other threads are blocked – since there is only one permit
  2. interrupting each of the thread by calling the interrupt() method; blocked threads will throw an InterruptedException when this method is called
  3. each of the blocked threads will then break out of the while loop, since the exception handling code contains the break; statement
This graceful shutdown version of the application is called ThreadTerm.java. The code is shown below:
import java.util.concurrent.*;
public class ThreadTerm {
	private static Semaphore sm =
		new Semaphore(1,true);
	private static final int DELAY =
		100;
	private static Thread t1;
	private static Thread t2;
	private static ThreadTerm thrdterm;
Note that the exception handler for both the blocking acquire() method of the semaphore, and the Thread.sleep() delay method contains the break; statement. This will ensure that the thread will exit the loop as soon as possible after being interrupted.
private class Work1 implements
	Runnable {
	public void run() {
		char t = ‘0’;
		while (true) {
			if (t == ‘0’)
				try {
					sm.acquire();
				} catch (Exception ex) {
					break;
				}
			System.out.print(t);
			if (++t > ‘9’) {
				sm.release();
				t = ‘0’;
			}
			try {
				Thread.sleep(DELAY);
			} catch (Exception ex) {
				break;
			}
		} // of while
	}
}

private class Work2 implements
	Runnable {
	public void run() {
		char t = ‘a’;
		while (true) {
			if (t == ‘a’)
				try {
					sm.acquire();
				} catch (Exception ex) {
					break;
				}
			System.out.print(t);
			if (++t > ‘z’) {
				t = ‘a’;
				sm.release();
			}
			try {
				Thread.sleep(DELAY);
			} catch (Exception ex)
				{break;}
		} // of while
	}
}

public void startThreads() {
	t1 = new Thread(new Work1());
	t2 = new Thread(new Work2());
	t1.start();
	t2.start();
}
The main difference in the ThreadTerm code is within the main() method. Here, it checks the user input for a single ‘.’ dot. Once the ‘.’ is entered, the main application thread acquires the semaphore and interrupts each thread.

Since a thread will not release the one and only permit until it completes printing its sequence, you can be sure that there will be no partially printed sequence when the application shuts down.

public static void main(
	String [] args) throws Exception {
	thrdterm = new ThreadTerm();
	thrdterm.startThreads();
	char inkey = ‘*’;
	while (inkey != ‘.’) {
		inkey =
			(char) System.in.read();
	}
	sm.acquire();
	t1.interrupt();
	t2.interrupt();
}
}
Compile and run the ThreadTerm example. You should see output similar to the ThreadSync example before. Now, type a ‘.’, followed by the Enter key. This will start the graceful shutdown. You will see that the application completes its printing before terminating.

Figure 6 displays the output from a typical execution of the ThreadTerm application: you can see that the threads complete the printing of their sequences before terminating. There is no need to hit the Ctrl-C or break key. (The break in the printing is caused by the echo of the ‘.’ character entry.)

Figure 6: Result of running ThreadTerm
Figure 6: Result of running ThreadTerm

Conclusions

Threads in the Java programming language can be used by applications to perform more than one task simultaneously.

Threads are frequently used to improve overall application responsiveness, and to utilize the CPU productively while lengthy I/O operations are in progress.

When writing multi-thread programs, one must be careful to synchronize any access to shared resources between threads.

This example introduced the Semaphore class from the java.util.concurrent package of JDK 1.5. This class is a ready-to-use mechanism for thread synchronization.


Sing Li is a consultant, trainer and freelance writer specialising in Java, Windows NT and distributed object technologies. Li’s most recent publication is Early Adopter JXTA: Peer-to-Peer Computing with Java from Wrox Press.


Modern thread mapping

In the very early days of the Java programming language, some operating systems did not have threads that were readily usable by a Java VM (some did not have system threads at all). On these systems, some Java VMs actually created their own thread execution engine within the VM’s own process. This often created tricky synchronization problems.

Thankfully, on almost all modern Win32 or *nix systems, the Java threads within a Java VM map one-to-one to the threads provided by the operating system.

You might also like...

Comments

About the author

Sing Li United States

Sing Li has been writing software, and writing about software for twenty plus years. His specialities include scalable distributed computing systems and peer-to-peer technologies. He now spends ...

Interested in writing for us? Find out more.

Contribute

Why not write for us? Or you could submit an event or a user group in your area. Alternatively just tell us what you think!

Our tools

We've got automatic conversion tools to convert C# to VB.NET, VB.NET to C#. Also you can compress javascript and compress css and generate sql connection strings.

“Better train people and risk they leave – than do nothing and risk they stay.” - Anonymous