Using multiple threads can make your programs more responsive and your code cleaner, but getting them to work correctly can be tricky. Java makes the task (pun intended) as easy as possible and is the first language to offer portable threading support.
Concurrent programming techniques have been around for many years, but have not seen widespread use until recently for a number of reasons. Traditional concurrent programs used multiple processes that required expensive IPC (interprocess communication) mechanisms and were severely platform dependent. If you could fork with the best of them on Unix, that wouldnt get you anywhere on VAX/VMS. Furthermore, creating processes on Unix was enough of a performance hit, but on many other operating systems it was abysmally slow and to be avoided whenever possible. Operating system designers got the message and invented lightweight processes, which evolved into what we know as threads today.
A thread is an execution path through a program, and there can be many threads active in a single process, all sharing the same process space. Using threads solved the performance hit of creating and managing separate processes every time concurrency was called for, but there remained the portability issue. The solution was of course to make threads part of the programming language, instead of leaving the job to the operating system or competing library vendors. This is what Java has achieved with a large degree of success.
Threaded programming is a complex affair. In this article, Ill scratch enough of the surface to make you comfortable managing independent tasks and shared resources concurrently in Java.
The Benefits of Concurrency
The motivation for the concurrent programmer is simple: better throughput. For example, separating the tasks of computation and presentation into distinct threads makes for a more responsive interactive program, because users can still press buttons and fill in text fields while awaiting the result of a pending request. On systems with multiple processors, separate threads can be scheduled to run simultaneously. Even on single processor systems, good thread scheduling algorithms make separate tasks appear to run concurrently to the human eye. A web server, for example, spawns separate threads to process incoming HTTP requests so one request doesnt have to wait for an unrelated one to finish before it gets a response. A web browser can use separate threads to display different parts of a web page.
Programmers like threads because they simplify code. That may sound a bit contradictory in light of the first sentence two paragraphs above. A language that supports concurrency shifts the bulk of the details of managing locks and inter-thread communication from your code to your platform, so all you need to do is to define your tasks, along with a minimal amount infrastructure. Getting the glue between threads to work is the tricky part, but the code comes out clean.
The program in Listing 1 shows one way to create and launch separate threads, which is to extend the Thread class and override the run method. The MyThread constructor expects a message and a count representing how many times to display it. The Thread class has a constructor that accepts an optional thread name. This feature isnt used much in practice, but I use it here to hold the message each thread is supposed to repeatedly print to the console screen. My run method just displays that message count times. To launch a thread, I call Thread.start, which in turn invokes MyThread.run automatically. As you can see, all but one of the repetitions of DessertTopping appear before the FloorWax thread gets a chance to execute. Your output may vary and is a function of the thread-scheduling algorithm used on your Java platform.
Sometimes you may want a thread to explicitly give other threads a chance to run, instead of hogging all the processor time it possibly can. You can do such cooperative multitasking with the Thread.yield static method, as illustrated in Listing 2. The net effect here is that each thread just takes its turn displaying a single instance of its message.
Another way to let threads share processor time is to pause them for a period of time. The static method Thread.sleep suspends the thread for at least the number of milliseconds specified by its argument so other threads can run (see Listing 3). Thread.sleep can throw an InterruptedException (explained below), but it wont happen in this example, so I just ignore it. This time the interleaving of the output appears more random than in the previous examples.
How many threads do you suppose are active at any one time in the examples above? The answer is one, two, or three (not zero, one, or two). When you start the virtual machine with the command
java Independentthe main method executes in an initial thread, called the main thread. Listing 4 illustrates this by displaying information about each thread. After t1 starts, there are two threads active, and finally three after t2 begins. To determine all the threads in a program requires access to their thread groups. All threads belong to a thread group [1]. Since I didnt explicitly create any thread groups, all threads belong to the default thread group (named main). The method ThreadGroup.activeCount returns the total number of threads in the group that havent yet terminated, and ThreadGroup.enumerate fills an array with the all the threads currently running and returns the count of the same. Thread.join waits until its thread completes, if necessary, before returning. The ThreadGroup.toString method displays a threads name, priority [2], and thread group.
Another way to define a thread is to first define a task to implement the Runnable interface, which defines a single run method, and then to pass that task to an alternate Thread constructor, as shown in Listing 5. This approach is attractive for a couple of reasons. From a conceptual point of view, it makes sense to separate a task from the thread that runs it. From a more pragmatic perspective, you need this technique if your task class extends another class, since Java only supports single inheritance.
Thread States
You might be wondering how the output in the previous examples came out the way it did. Why didnt the characters of the individual lines get interleaved? The reason is that many methods in java.lang.io cause a thread to block, which means the thread is put on hold until the call is complete. If you were to rewrite Listing 3 to print the characters individually using print instead of println, then such interleaving does occur (see Listing 6).
Being blocked is just one of the several states a thread can be in. As Figure 1 illustrates, when a thread is started, it enters the Runnable (or Ready) state, which means it is eligible to be chosen to execute by the JVMs thread scheduler. If the thread calls a blocking method, it enters the Blocked state until the blocking operation is complete, after which it is eligible to be rescheduled. Similarly, when a threads code encounters a call to Thread.sleep, it enters the Sleeping state for the specified amount of time, and then becomes Runnable. When the scheduler suspends a thread so another can run, the first thread moves from the Running to the Runnable state. Only Runnable threads can be scheduled for execution. A thread is considered dead when its run method returns. Once dead, a thread cannot be restarted, although its associated Thread object is still available until it is garbage-collected.
The acceptable way to stop a thread is to politely ask it to stop itself. A typical situation where you need to stop a thread prematurely is in interactive applications. For example, if a database query is taking too long and you want to abort, you need to be able to press a Cancel button and have the query stop. The program in Listing 7 contains a first attempt to stop a thread. The main thread spawns a worker thread that displays a count every second and then waits for the user to press Enter. The Counter class has a cancel method that sets the boolean field cancelled. The loop in Counter.run checks cancelled before printing on each iteration so it knows when to exit. Ill explain shortly why this is not a generally safe technique for stopping a thread.
Did you notice that the main thread terminated before the worker thread did in Listing 7? The application keeps running as long as any threads are still active. Well, almost any thread. There are two types of threads in Java: user threads and daemon (pronounced DAY-mun) threads. The threads Ive shown you so far are user threads. The difference between the two thread types is that if there are only daemon threads active, the application can terminate. You make a daemon thread by simply calling setDaemon(true) on an existing thread. By making the Counter thread in the previous example a daemon thread, I can halt the application without the canceling mechanism (see Listing 8).
A very common technique for stopping a thread is to use Thread.interrupt, which is an official way of getting a threads attention. When you interrupt a thread, it doesnt really stop it just causes a call to Thread.interrupted to return true when invoked in that thread [3]. The program in Listing 9 uses this feature to kill the Counter loop. This program clarifies why Thread.sleep may throw an InterruptedException, and what to do about it. If a thread is sleeping, it cant be directly interrupted, since it isnt running. Instead, it moves to the Runnable state and waits its turn to execute. When it runs, execution resumes in the InterruptedException handler associated with the pending call to Thread.sleep. The usual thing to do is to call interrupt again, since the initial call gave way to the exception. The loop will then complete on its next iteration and run will return [4].
Thread Safety
One of the most difficult concepts to grasp in concurrent programming is how to share resources among separate tasks. Whenever two or more threads access the same object, you need to ensure that those threads take turns; otherwise your data can end up in an inconsistent state. Code that guards against this phenomenon is said to be thread safe. To illustrate, consider a Book object in a program that supports a public library. Suppose each book has title, author, and borrower fields. If two threads try to check out the same book at the same time, theres no telling what could happen. Perhaps the thread that executed last would win the privilege of being recorded as the borrower, but both threads would think they completed the transaction successfully. Bad news.
The Java threading model uses monitors to prevent threads from accessing shared data simultaneously. A monitor is a mechanism that establishes what are known as critical sections in code. Only one thread is allowed in a critical section at a time. A monitor is always associated with an object. The monitor for an object protects all the critical sections in the code associated with that object as a unit (so one thread cant be in one critical section while another thread is in another section). You declare critical sections with the synchronized keyword. Synchronization is expensive (four to six times more than non-synchronized methods, they say), so you should use it sparingly.
The only thread-sensitive data in Listing 10 is the borrower field. The other two fields are read-only and are therefore inherently thread-safe. To protect access to borrower, I do two important things:
1. declare it to be private (otherwise there is no protection whatever!), and
2. declare all methods that use it to be synchronized including the accessor method getBorrower (otherwise it would be possible for one thread to get the borrower right before another thread changes it, making the data invalid).
To execute one of these methods, a thread must obtain a lock on the associated object. (All Java objects have a hidden lock field). If another thread has the lock, the requesting thread waits until it becomes available. Once a thread has the lock (which is called being in the monitor), no other thread can execute any synchronized code. The thread in the monitor has exclusive access to the synchronized block until it leaves it and releases the lock. This is why checkout and checkIn can call isAvailable without any problems.
Since it is always a good idea to keep critical sections as small as possible, you should declare an entire method synchronized only when absolutely necessary. The program in Listing 11 shows how to declare a synchronized block, while at the same time fixing the problem with stopping a thread via the cancel method in Listing 7. Since multiple threads executing Counter objects could conceivably be running at the same time, then the data fields need to be protected. Since cancel is a one-line method, there is no problem declaring it synchronized, but run must not be synchronized. Since it has an infinite loop, it would never be interrupted if it ran in the monitor. For this reason, I place only the two statements that require protection in a synchronized block on the current object. (Remember, a monitor always requires an object to lock.)
It is possible to declare code in static methods as a critical section. The associated monitor simply obtains a lock on the class object. Listing 12 illustrates this by fixing the interleaved output problem shown in Listing 7. There is no shared data in this case, so no non-static synchronized methods are necessary. Instead, I just make the static method display synchronized. When MyThread.run calls MyThread.display, the thread obtains a lock on the object MyThread.class(), so the output proceeds uninterrupted. If I were to make display non-static, it would not prevent the interleaving, because the locks would be on two unrelated objects (t1 and t2).
Communication between Threads
Synchronization keeps threads from getting in each others way, but it doesnt allow threads to talk to each other. Sometimes a thread needs to wait on a condition brought about by another thread. Such is the case in typical producer-consumer scenarios, like real-time queuing systems. The program in Listing 13 uses a Queue to store messages created by Producer threads. Consumer threads wait until something is available in the queue to process. In this case, for simplicity, I just traffic in Integer objects, but the Queue class, which uses the Java 2 LinkedList collection class, doesnt need to change [5].
In order to protect the queue, each thread synchronizes its own access to it. A consumer thread first checks to see if there is something to consume. If there isnt, it waits until there is. The wait method (defined in the Object class), which for thread safety must itself execute in a monitor, atomically releases its lock and blocks until it is notified by another thread [6]. After a producer inserts an entry in the queue, it calls notify, which causes it to block, and the thread scheduler wakes up another arbitrarily chosen thread that is in a wait state for the same monitor. If the newly awakened thread is a consumer, then its while loop completes so it can consume an Integer. If not, another producer will insert another entry into the queue and do another notify. Eventually a consumer thread will get notified. It is not possible to wake up a specific thread. If you want to notify multiple threads, then call notifyAll instead of notify. That would be counterproductive in this example because only one consumer can process a queue entry.
The program in Listing 13 of course doesnt halt. (I pressed Control-C to abort it.) You could use the interrupt/interrupted technique mentioned earlier to halt from the main program, but Listing 14 proposes a better solution [7]. I use a counter to track how many producer threads are active, so when they all finish the consumers can end gracefully. When the queue is empty, each consumer checks to see if there are any producers left running. If not, their run method simply returns and the application ends. Since there are now two conditions that the consumer threads are waiting for (either a new queue entry or zero producers remaining), I need to notify on both conditions. A call to notifyAll after a producer decrements its counter ensures that both threads are alerted when all the producers have terminated.
Some things to remember:
1) wait and notify must always be called in a monitor.
2) You must execute wait and notify on the same object you synchronize on.
3) Both methods cause their threads to block (to allow other threads to run) and to enter the monitors wait list. If no threads are waiting, notify simply returns. The original threads reacquire their locks when they resume.
4) In general you should test for a wait condition in a loop. (The condition may have changed between the time the thread was awakened and the time you re-test.)
Summary
Threads allow you to improve perceived throughput of your programs, as well as divide your code into cleanly defined tasks, but crafting thread-safe code can be tricky. There isnt room here to talk about other issues such as communication through callbacks, deadlock, and thread-local storage. I hope, however, that this article has helped you understand how threads work, and why you should care.
Notes
[1] ThreadGroups can contain other ThreadGroups, forming a tree with the default group as root.
[2] Priorities are integers in the range [Thread.MIN_PRIORITY, Thread.MAX_PRIORITY]. What these priorities actually mean depends on your platform. Using native threads, where the JVM uses the underlying operating systems threading facilities for concurrency, the number of priorities may differ, so two priority numbers can be mapped to the same native priority. The only assumption you can really make is that threads with higher priority may be executed in preference to threads with lower priority.
[3] Thread.interrupted is static and works on the current thread. There is a non-static isInterrupted method also.
[4] Notice in this example that I reverted to extending Counter from Thread. If you use the Runnable approach, just call Thread.getCurrentThread().interrupt().
[5] For more on Java 2 Collections, see the September 2000 installment of import java.*. I wanted to use a synchronized wrapper for the queue, but it only supports the List interface, so the LinkedList functions arent available.
[6] There are overloads for wait that allow you to specify a timeout value.
[7] Id like to thank Michael Seaver of Novell for help with this solution.
Chuck Allison is a long-time columnist with CUJ. During the day he does Internet-related development in Java and C++ as a Software Engineering Senior in the Custom Development Department at Novell, Inc. in Provo, Utah. He was a contributing member of the C++ standards committee for most of the 1990s and authored the book C & C++ Codes Capsules: A Guide for Practitioners (Prentice-Hall, 1998). He has taught mathematics and computer science at six western colleges and universities and at many corporations throughout the U.S. You can email Chuck at chuck@freshsources.com.