Multi-threaded programming is the source of many headaches for developers, particularly when large numbers of threads are running. Managing these threads is a difficult task. As the number of threads increases, system performance is affected, and it is often advantageous to adjust thread priorities of specific threads, suspend certain threads, or stop them entirely. While it is certainly possible to do this on an individual basis, a simpler solution is to use thread groups, which makes it possible to apply changes to a large number of threads at once. Thread groups also offer other advantages, such as keeping an automatic tally of how many active threads belong to a group, as well as an opportunity to improve thread debugging, by catching uncaught exceptions that would normally be missed.
Grouping Related Threads Together
The need for grouping threads together is very strong. In most applications, threads can be divided into several groups. Some, for example, perform background programming, while others update the user interface and respond to user interaction, or perform tasks in the foreground. To maintain a list of threads, an array or a data structure from the Java Collections framework might be used — but a better alternative is the
java.lang.ThreadGroup |
The
ThreadGroup |
ThreadGroup |
enumerate(..) |
run() |
Figure 1. Thread groups, of course, allow many threads to be grouped and nested.
Creating a thread group is extremely simple. There are two constructors, one of which takes as a parameter a String (which names a group) and the other, which also takes a parent group. This allows groups to be nested (a subgroup with a group as a parent).
public ThreadGroup ( String name )
public ThreadGroup ( ThreadGroup parent, String name)
Threads are grouped together when first constructed. The
java.lang.Thread |
ThreadGroup |
ThreadGroup somegroup = new ThreadGroup ("GroupA");
Thread t = new Thread ( somegroup, "dummy-thread");
Accessing Individual Threads
If access to individual threads is necessary, a list of active threads can be obtained, by calling the
ThreadGroup.enumerate(..) |
ThreadGroup |
enumerate |
ThreadGroup |
ThreadGroup.activeCount() |
ThreadGroup.activeGroupCount() |
Let’s look at an example of how this would be done. Suppose we have an existing group
theGroup |
// Get the number of active threads
int activeThreadCount = theGroup.activeCount();
// Create an array of threads, to hold all of the active threads
Thread[] threadArray = new Thread[activeThreadCount];
// Copy to array
theGroup.enumerate(threadArray);
Once a list is obtained, operations may be performed selectively on each thread, or if a custom subclass of a thread was used, it may be cast as such. For example, if I created a
WorkerThread |
java.lang.Thread |
WorkerThread |
for (int i = 0; i < threadArray.length; i++)
{
// *Always* check using an instanceof operator, to prevent
// a java.lang.ClassCastException being thrown
if (threadArray[i] instanceof WorkerThread)
{
// Perform cast
WorkerThread worker = (WorkerThread) threadArray[i] ;
Object obj = worker.getData();
// do something with data …..
}
}
Performing Thread Operations En Masse
While working with a thread individually is necessary sometimes, a simpler alternative is to perform operations en masse, using thread groups. The ThreadGroup class offers several methods that represent common thread operations, such as suspending, resuming, interrupting, and even stopping threads. Alas, there is no start method for a thread group — which would be a useful shortcut. Nonetheless, the other methods make it quite simple to perform thread operations collectively, rather than individually.
You might ask, why would this be handy? Consider the amount of code required to access an array of active threads and then looping through each and every element of the array to perform the operation. Now, consider how much code is required to suspend all active threads in a group:
// ThreadGroup theGroup
theGroup.suspend(); // interrupt all active threads
The following methods of the ThreadGroup class apply to all active threads:
- ThreadGroup.stop();
- ThreadGroup.suspend();
- ThreadGroup.interrupt();
- ThreadGroup.resume();
Note: Readers should be aware that the stop, suspend, and resume methods have been deprecated as of JDK 1.2, due to a potential problem which may arise when the
stop() |
suspend() |
java.lang.Thread |
Modifying Thread Priorities
While it isn’t possible to specify the priority of a group and all the threads within it (though this functionality would be very handy), it is possible to impose a cap on the upper limit of a thread group’s priority, by calling the
ThreadGroup.setMaxPriority(int) |
theGroup.setMaxPriority (7);
Improving Error Handling with Thread Groups
What’s the most annoying part of running Java software for users? My vote would be uncaught exceptions, which occur only at runtime and not compile time, and almost never during testing and debugging. The exception-handling features of Java are very good, but methods are not forced to declare methods that extend the
java.lang.RuntimeException |
Sure you can add a try/catch block that catches a
RuntimeException |
run() |
Thread |
Runnable |
The
ThreadGroup |
uncaughtException(Thread |
Listing 1. Creating a custom handler.
By creating an instance of this custom thread group and assigning all threads to it as they are created, you can be assured that any thread that causes an error will be logged, and a user-friendly error message displayed (rather than the default action of a decidedly nasty stack trace).
Summary
Thread groups can make life much simpler for programmers, as well as improving error handling. While threads can be stored collectively in an array or Vector, the additional functionality provided by thread groups make them a more attractive option. By grouping threads collectively, you can also have clearer code and an easy way to track how many threads are active.
About the author
David Reilly is a software engineer and freelance technical writer living in Australia. A Sun Certified Java 1.1 Programmer, his research interests include the Java programming language, networking & distributed systems, and software agents. He can be reached via email at java@davidreilly.com or through his personal site.