Improving Thread Management with Thread Groups
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
class is a special type of container, designed specifically with threads in mind. Thread groups may contain individual threads, as well as subgroups of threads. The
class provides methods to access individual elements of the group via the
method, which copies either threads or thread groups into an array. It also provides methods to determine the number of active threads (i.e., threads that have not exited their
method). Additionally the thread group class provides a way to access a count of the number of active groups (i.e., the number of groups and subgroups with active threads).
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
class provides several constructors, which take a
as a parameter. Of course, threads can be created without assigning them to a specific group, but they will be grouped nonetheless. A group is created by the JVM at startup, which runs the primary application thread. So unless you select a specific group, one will be set for you. The following example shows the construction of a thread group and how to specify the group of a thread:
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
method. There are several overloaded versions of this method, which also allow you to select groups that are subgroups of a given
object, as well as to recursively search in all subgroups for a list of threads. All of the
methods require you to create an instance of a Thread array, or a
array, with enough size to hold each item. To determine the number of active threads/groups, you can call either the
Let's look at an example of how this would be done. Suppose we have an existing group
, with an uncertain number of threads (since some active threads may have terminated, and the number is not known).
// 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
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
class which extended
, it could be cast back to a
, and then operations could be performed (such as accessing member variables or methods).
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:
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
are invoked. Any object locks held by these threads are immediately released (to prevent a deadlock arising when another thread tries to take out a lock owned by a dead or dormant thread). However, the object protected by the lock might be left in an inconsistent state. For this reason, these methods should be used sparingly and only where absolutely necessary.
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
method. For example, suppose we wanted a limit of 7, we'd use code similar to the following:
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
. This means you won't catch them during compilation, and as they happen irregularly, you may not notice them during testing.
Sure you can add a try/catch block that catches a
method of a
subclass, but what if you want to handle error conditions in a central location, within one thread? The answer is, again, using thread groups!
class defines a method,
, which is invoked whenever a thread fails to catch an exception at runtime. By default, this method doesn't do anything useful, but by overriding this method you can create a custom handler. Let's look at a sample class, which logs the exceptions of any threads and then terminates.
thread, Throwable exception)
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).
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 email@example.com or through his personal site.